Improved handling ws connection and redirect
This commit is contained in:
parent
53a07b21b0
commit
0317a3343c
10 changed files with 100 additions and 50 deletions
|
@ -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("/")
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
})
|
})
|
||||||
|
|
|
@ -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"],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -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>}`
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue