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(() => { useEffect(() => {
if (!launching || launchTime >= 1) return if (!launching || launchTime > 0) return
router.push("/gamefield") socket.emit("starting")
}, [launching, launchTime, router]) }, [launching, launchTime, router])
useEffect(() => { useEffect(() => {
if (!launching) return setLaunchTime(3) if (!launching) return setLaunchTime(3)
if (launchTime === 0) return if (launchTime < 0) return
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
setLaunchTime((e) => e - 1) setLaunchTime((e) => e - 1)
@ -67,16 +67,29 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) {
socket.emit("update", full) socket.emit("update", full)
}, [full, payload?.game?.id, isConnected]) }, [full, payload?.game?.id, isConnected])
useEffect(() => {
if (
typeof payload?.game?.state !== "string" ||
payload?.game?.state === "lobby"
)
return
router.push("gamefield")
})
return ( return (
<div className="mx-32 flex flex-col self-stretch rounded-3xl bg-gray-400"> <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"> <div className="flex items-center justify-between border-b-2 border-slate-900">
<Icon src="speech_bubble.png">Chat</Icon> <Icon src="speech_bubble.png">Chat</Icon>
<h1 className="font-farro text-5xl font-medium"> <h1 className="font-farro text-5xl font-medium">
{launching ? ( {launching ? (
<WithDots>{"Game is starting in " + launchTime}</WithDots> <WithDots>
{launchTime < 0
? "Game starts"
: "Game is starting in " + launchTime}
</WithDots>
) : ( ) : (
<> <>
Game-PIN:{" "} {"Game-PIN: "}
{isConnected ? ( {isConnected ? (
<span className="underline">{payload?.gamePin ?? "----"}</span> <span className="underline">{payload?.gamePin ?? "----"}</span>
) : ( ) : (
@ -114,8 +127,8 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) {
disabled={launching} disabled={launching}
onClick={() => { onClick={() => {
leave(async () => { leave(async () => {
await router.push("/")
reset() 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 { useDrawProps } from "./useDrawProps"
import { socket } from "@lib/socket" import { socket } from "@lib/socket"
import { useEffect, useRef, useState } from "react" import { useEffect, useRef, useState } from "react"
@ -108,7 +108,7 @@ export const useDraw = () => {
drawLine({ prevPoint, currentPoint, ctx, color }) drawLine({ prevPoint, currentPoint, ctx, color })
}) })
socket.on("clear", clear) socket.on("canvas-clear", clear)
return () => { return () => {
socket.removeAllListeners() socket.removeAllListeners()

View file

@ -45,6 +45,7 @@ export type Action = {
full: (newProps: GamePropsSchema) => void full: (newProps: GamePropsSchema) => void
leave: (cb: () => void) => void leave: (cb: () => void) => void
setIsReady: (payload: { i: number; isReady: boolean }) => void setIsReady: (payload: { i: number; isReady: boolean }) => void
starting: () => void
setIsConnected: (payload: { i: number; isConnected: boolean }) => void setIsConnected: (payload: { i: number; isConnected: boolean }) => void
reset: () => void reset: () => void
} }
@ -159,6 +160,17 @@ export const useGameProps = create<State & Action>()(
state.userStates[i].isConnected = true 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 }) => setIsConnected: ({ i, isConnected }) =>
set( set(
produce((state: State) => { produce((state: State) => {

View file

@ -1,5 +1,7 @@
import { isAuthenticated } from "../pages/start"
import { useGameProps } from "./useGameProps" import { useGameProps } from "./useGameProps"
import { socket } from "@lib/socket" import { socket } from "@lib/socket"
import { GamePropsSchema } from "@lib/zodSchemas"
import status from "http-status" import status from "http-status"
import { useSession } from "next-auth/react" import { useSession } from "next-auth/react"
import { useRouter } from "next/router" import { useRouter } from "next/router"
@ -16,6 +18,7 @@ function useSocket() {
setSetting, setSetting,
full, full,
setIsReady, setIsReady,
starting,
setIsConnected, setIsConnected,
} = useGameProps() } = useGameProps()
const { data: session } = useSession() const { data: session } = useSession()
@ -27,6 +30,10 @@ function useSocket() {
if (!isIndex) return { i: undefined, isIndex } if (!isIndex) return { i: undefined, isIndex }
return { i, isIndex } return { i, isIndex }
}, [payload?.users, session?.user?.id]) }, [payload?.users, session?.user?.id])
const isConnected = useMemo(
() => (isIndex ? userStates[i].isConnected : isConnectedState),
[i, isConnectedState, isIndex, userStates]
)
useEffect(() => { useEffect(() => {
if (!isIndex) return if (!isIndex) return
@ -37,8 +44,6 @@ function useSocket() {
}, [i, isConnectedState, isIndex, setIsConnected]) }, [i, isConnectedState, isIndex, setIsConnected])
useEffect(() => { useEffect(() => {
if (!session?.user.id) return
socket.on("connect", () => { socket.on("connect", () => {
console.log("connected") console.log("connected")
toast.dismiss("connect_error") toast.dismiss("connect_error")
@ -73,7 +78,7 @@ function useSocket() {
socket.on("playerEvent", (event) => { socket.on("playerEvent", (event) => {
const { type, i } = event const { type, i } = event
let message: string let message: string
console.log(type) console.log("playerEvent", type)
switch (type) { switch (type) {
case "disconnect": case "disconnect":
setIsConnected({ setIsConnected({
@ -115,6 +120,8 @@ function useSocket() {
socket.on("isReady", setIsReady) socket.on("isReady", setIsReady)
socket.on("starting", starting)
socket.on("disconnect", () => { socket.on("disconnect", () => {
console.log("disconnect") console.log("disconnect")
setIsConnectedState(false) setIsConnectedState(false)
@ -125,27 +132,37 @@ function useSocket() {
} }
}, [ }, [
full, full,
payload?.game?.id,
router, router,
session?.user.id, session?.user.id,
setIsConnected, setIsConnected,
setIsReady, setIsReady,
setPlayer, setPlayer,
setSetting, setSetting,
starting,
userStates, userStates,
]) ])
// useEffect(() => { useEffect(() => {
// if (!isConnected) return if (!payload?.game?.id) {
// let count = 0 socket.disconnect()
// const interval = setInterval(() => { fetch("/api/game/running", {
// const start = Date.now() method: "GET",
// socket.volatile.emit("ping", ++count, (count) => { })
// const duration = Date.now() - start .then(isAuthenticated)
// console.log("ping", count, duration) .then((game) => GamePropsSchema.parse(game))
// }) .then((res) => full(res))
// }, 5000) .catch()
// return () => clearInterval(interval) return
// }, [isConnected]) }
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 } return { isConnected: isIndex ? userStates[i].isConnected : isConnectedState }
} }

View file

@ -47,20 +47,22 @@ export interface ServerToClientEvents {
"get-canvas-state": () => void "get-canvas-state": () => void
"canvas-state-from-server": (state: string, userIndex: number) => void "canvas-state-from-server": (state: string, userIndex: number) => void
"draw-line": (props: DrawLineProps, userIndex: number) => void "draw-line": (props: DrawLineProps, userIndex: number) => void
clear: () => void "canvas-clear": () => void
starting: () => void
} }
export interface ClientToServerEvents { export interface ClientToServerEvents {
update: (callback: (game: GamePropsSchema) => void) => void update: (callback: (game: GamePropsSchema) => void) => void
isReady: (isReady: boolean) => void isReady: (isReady: boolean) => void
isConnected: (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 join: (withAck: (ack: boolean) => void) => void
gameSetting: (payload: GameSettings, callback: (hash: string) => void) => void gameSetting: (payload: GameSettings, callback: (hash: string) => void) => void
leave: (withAck: (ack: boolean) => void) => void leave: (withAck: (ack: boolean) => void) => void
"canvas-state": (state: string) => void "canvas-state": (state: string) => void
"draw-line": (props: DrawLineProps) => void "draw-line": (props: DrawLineProps) => void
clear: () => void "canvas-clear": () => void
starting: () => void
} }
interface InterServerEvents { interface InterServerEvents {

View file

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

View file

@ -55,7 +55,7 @@ export default async function join(
if (games.length) { if (games.length) {
return sendResponse(req, res, { return sendResponse(req, res, {
message: "Spieler ist bereits in Spiel!", message: "Spieler ist bereits in Spiel!",
statusCode: 409, redirectUrl: "/api/game/running",
type: ["infoCyan"], type: ["infoCyan"],
}) })
} }

View file

@ -2,7 +2,6 @@ import {
NextApiResponseWithSocket, NextApiResponseWithSocket,
sServer, sServer,
} from "../../interfaces/NextApiSocket" } from "../../interfaces/NextApiSocket"
import { DrawLineProps } from "../../interfaces/frontend"
import { import {
composeBody, composeBody,
gameSelects, gameSelects,
@ -108,9 +107,7 @@ const SocketHandler = async (
socket.to(game.id).emit("gameSetting", payload, hash) socket.to(game.id).emit("gameSetting", payload, hash)
}) })
socket.on("ping", (count, callback) => { socket.on("ping", (callback) => callback())
callback(count)
})
socket.on("leave", async (cb) => { socket.on("leave", async (cb) => {
if (!socket.data.gameId || !socket.data.user?.id) return cb(false) 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) .emit("canvas-state-from-server", state, socket.data.index)
}) })
socket.on( socket.on("draw-line", ({ prevPoint, currentPoint, color }) => {
"draw-line", if (!socket.data.gameId || !socket.data.index) return
({ prevPoint, currentPoint, color }: DrawLineProps) => { socket
if (!socket.data.gameId || !socket.data.index) return .to(socket.data.gameId)
socket .emit(
.to(socket.data.gameId) "draw-line",
.emit( { prevPoint, currentPoint, color },
"draw-line", socket.data.index
{ prevPoint, currentPoint, color }, )
socket.data.index })
)
}
)
socket.on("clear", () => { socket.on("canvas-clear", () => {
if (!socket.data.gameId) return 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 () => { socket.on("disconnecting", async () => {
@ -225,7 +230,6 @@ const SocketHandler = async (
}) })
socket.on("disconnect", () => { socket.on("disconnect", () => {
// socket.rooms.size === 0
logging("Disconnect: " + socket.id, ["debug"], socket.request) 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 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>}` export type GameStateType = `${z.infer<typeof GameStateSchema>}`

View file

@ -69,7 +69,8 @@ model VerificationToken {
} }
enum GameState { enum GameState {
launching lobby
starting
running running
ended ended
} }
@ -78,7 +79,7 @@ model Game {
id String @id @default(cuid()) id String @id @default(cuid())
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
state GameState @default(launching) state GameState @default(lobby)
allowSpectators Boolean @default(true) allowSpectators Boolean @default(true)
allowSpecials Boolean @default(true) allowSpecials Boolean @default(true)
allowChat Boolean @default(true) allowChat Boolean @default(true)