From 12295b316fdb308274fd57031a9351f470e703a5 Mon Sep 17 00:00:00 2001 From: aronmal Date: Thu, 27 Apr 2023 18:57:00 +0200 Subject: [PATCH] Some after work from last commit --- leaky-ships/components/Lobby/LobbyFrame.tsx | 32 ++++++++-- leaky-ships/lib/backend/logging.ts | 3 +- leaky-ships/lib/hooks/useGameState.tsx | 38 ++++++++---- leaky-ships/lib/hooks/useSocket.ts | 2 +- leaky-ships/lib/socket.ts | 2 +- leaky-ships/pages/_app.tsx | 1 + leaky-ships/pages/api/auth/[...nextauth].ts | 7 +-- leaky-ships/pages/api/game/join.ts | 7 ++- leaky-ships/pages/api/game/running.ts | 2 +- leaky-ships/pages/api/{ws2.ts => ws.ts} | 41 +++++++++---- .../pages/{game/index.tsx => game.tsx} | 29 ++------- .../pages/{lobby/[gameId].tsx => lobby.tsx} | 34 ++--------- leaky-ships/pages/lobby/index.tsx | 60 ------------------- leaky-ships/pages/start.tsx | 48 +++++++-------- 14 files changed, 130 insertions(+), 176 deletions(-) rename leaky-ships/pages/api/{ws2.ts => ws.ts} (60%) rename leaky-ships/pages/{game/index.tsx => game.tsx} (55%) rename leaky-ships/pages/{lobby/[gameId].tsx => lobby.tsx} (51%) delete mode 100644 leaky-ships/pages/lobby/index.tsx diff --git a/leaky-ships/components/Lobby/LobbyFrame.tsx b/leaky-ships/components/Lobby/LobbyFrame.tsx index 4c0becf..8ca7d0f 100644 --- a/leaky-ships/components/Lobby/LobbyFrame.tsx +++ b/leaky-ships/components/Lobby/LobbyFrame.tsx @@ -1,13 +1,32 @@ import Icon from "./Icon" import Player from "./Player" +import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons" +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" import useGameState from "@lib/hooks/useGameState" import useSocket from "@lib/hooks/useSocket" +import { useRouter } from "next/router" import { Fragment, useEffect, useState } from "react" +import { toast } from "react-toastify" function LobbyFrame({ openSettings }: { openSettings: () => void }) { const { payload } = useGameState().gameProps - useSocket() - const [dots, setDots] = useState(1) + const { isConnected, hasJoined } = useSocket() + const [dots, setDots] = useState(3) + const router = useRouter() + + useEffect(() => { + if (isConnected && hasJoined) return + const timeout = setTimeout(() => { + toast("Es konnte keine Echtzeitverbindung aufgebaute werden.", { + toastId: "connection_error", + type: "error", + autoClose: 10000, + }) + router.push("/start") + }, 15000) + + return () => clearTimeout(timeout) + }, []) useEffect(() => { if (payload?.player2) return @@ -21,7 +40,11 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) { Chat

Game-PIN:{" "} - {payload?.gamePin?.pin ?? "---"} + {isConnected ? ( + {payload?.gamePin?.pin ?? "---"} + ) : ( + + )}

Settings @@ -42,7 +65,8 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) { /> ) : (

- Warte auf Spieler 2 {Array.from(Array(dots), () => ".").join("")} + Warte auf {isConnected && hasJoined ? "Spieler 2" : "Verbindung"}{" "} + {Array.from(Array(dots), () => ".").join("")} {Array.from(Array(3 - dots), (_, i) => (   ))} diff --git a/leaky-ships/lib/backend/logging.ts b/leaky-ships/lib/backend/logging.ts index 56d4764..82b491b 100644 --- a/leaky-ships/lib/backend/logging.ts +++ b/leaky-ships/lib/backend/logging.ts @@ -1,5 +1,6 @@ import colors, { Color } from "colors" import fs from "fs" +import { IncomingMessage } from "http" import { NextApiRequest } from "next" colors.enable() @@ -38,7 +39,7 @@ async function logStartup() { async function logging( message: string, types: Logging[], - req?: NextApiRequest + req?: NextApiRequest | IncomingMessage ) { if (!started) await logStartup() const messages = { console: message, file: message } diff --git a/leaky-ships/lib/hooks/useGameState.tsx b/leaky-ships/lib/hooks/useGameState.tsx index ff549f8..99f854e 100644 --- a/leaky-ships/lib/hooks/useGameState.tsx +++ b/leaky-ships/lib/hooks/useGameState.tsx @@ -3,20 +3,19 @@ import { useSession } from "next-auth/react" import { Dispatch, ReactNode, - SetStateAction, createContext, useContext, useEffect, - useState, useMemo, + useReducer, } from "react" import { toast } from "react-toastify" -const initialValue = { payload: null, hash: null } +const initialValue: GamePropsSchema = { payload: null, hash: null } interface gameContext { gameProps: GamePropsSchema - setGameProps: Dispatch> + setGameProps: Dispatch } export const gameContext = createContext({ @@ -24,8 +23,30 @@ export const gameContext = createContext({ setGameProps: () => {}, }) +function updateProps(state: GamePropsSchema, body: GamePropsSchema) { + if (state.hash === body.hash) { + console.log("Everything up to date.") + return state + } else { + console.log("Update was needed.", state.hash, body.hash) + + if ( + state.payload?.game?.id && + state.payload?.game?.id !== body.payload?.game?.id + ) { + console.warn( + "Different gameId detected on update: ", + state.payload?.game?.id, + body.payload?.game?.id + ) + } + + return body + } +} + export function GameContextProvider({ children }: { children: ReactNode }) { - const [gameProps, setGameProps] = useState(initialValue) + const [gameProps, setGameProps] = useReducer(updateProps, initialValue) const value = useMemo( () => ({ gameProps, setGameProps }), @@ -35,15 +56,10 @@ export function GameContextProvider({ children }: { children: ReactNode }) { return {children} } -function useGameState(initial?: GamePropsSchema) { +function useGameState() { const { gameProps, setGameProps } = useContext(gameContext) const { data: session, status } = useSession() - useEffect(() => { - if (!initial) return - setGameProps(initial) - }, [initial, setGameProps]) - useEffect(() => { if (status === "loading") return if (!session) diff --git a/leaky-ships/lib/hooks/useSocket.ts b/leaky-ships/lib/hooks/useSocket.ts index de8ebcb..0b33951 100644 --- a/leaky-ships/lib/hooks/useSocket.ts +++ b/leaky-ships/lib/hooks/useSocket.ts @@ -52,7 +52,7 @@ function useSocket() { socket?.emit("authenticate", { token: `hello from ${session?.user.email}` }) }, [isConnected, status, session?.user.email]) - return socket + return { isConnected, hasJoined } } export default useSocket diff --git a/leaky-ships/lib/socket.ts b/leaky-ships/lib/socket.ts index 9e71296..bf08521 100644 --- a/leaky-ships/lib/socket.ts +++ b/leaky-ships/lib/socket.ts @@ -2,5 +2,5 @@ import { cSocket } from "../interfaces/NextApiSocket" import { io } from "socket.io-client" export const socket: cSocket = io({ - path: "/api/ws2", + path: "/api/ws", }) diff --git a/leaky-ships/pages/_app.tsx b/leaky-ships/pages/_app.tsx index 19b6a0c..f96645b 100644 --- a/leaky-ships/pages/_app.tsx +++ b/leaky-ships/pages/_app.tsx @@ -2,6 +2,7 @@ import "../styles/App.scss" import "../styles/globals.scss" import "../styles/grid2.scss" import "../styles/grid.scss" +import "@fortawesome/fontawesome-svg-core/styles.css" import { GameContextProvider } from "@lib/hooks/useGameState" import { SessionProvider } from "next-auth/react" import type { AppProps } from "next/app" diff --git a/leaky-ships/pages/api/auth/[...nextauth].ts b/leaky-ships/pages/api/auth/[...nextauth].ts index aa849b0..10ff3dc 100644 --- a/leaky-ships/pages/api/auth/[...nextauth].ts +++ b/leaky-ships/pages/api/auth/[...nextauth].ts @@ -7,17 +7,16 @@ import EmailProvider from "next-auth/providers/email" import { uniqueNamesGenerator, Config, - adjectives, animals, NumberDictionary, } from "unique-names-generator" -const numberDictionary = NumberDictionary.generate({ min: 0, max: 999 }) +const numberDictionary = NumberDictionary.generate({ min: 0, max: 9999 }) const customConfig: Config = { - dictionaries: [adjectives, animals, numberDictionary], + dictionaries: [animals, numberDictionary], separator: " ", style: "capital", - length: 3, + length: 2, } const options: NextAuthOptions = { diff --git a/leaky-ships/pages/api/game/join.ts b/leaky-ships/pages/api/game/join.ts index 3ff005f..faab065 100644 --- a/leaky-ships/pages/api/game/join.ts +++ b/leaky-ships/pages/api/game/join.ts @@ -4,6 +4,7 @@ import sendError from "@backend/sendError" import sendResponse from "@backend/sendResponse" import { rejectionErrors } from "@lib/backend/errors" import getPinFromBody from "@lib/backend/getPinFromBody" +import logging from "@lib/backend/logging" import prisma from "@lib/prisma" import { GamePropsSchema } from "@lib/zodSchemas" import type { NextApiRequest, NextApiResponse } from "next" @@ -57,7 +58,11 @@ export default async function join( type: ["debug", "infoCyan"], }) } catch (err: any) { - console.log("HERE".red, err.code, err.meta, err.message) + await logging( + "HERE".red + err.code + err.meta + err.message, + ["error"], + req + ) throw sendError(req, res, rejectionErrors.gameNotFound) } } diff --git a/leaky-ships/pages/api/game/running.ts b/leaky-ships/pages/api/game/running.ts index 9de3c29..8715721 100644 --- a/leaky-ships/pages/api/game/running.ts +++ b/leaky-ships/pages/api/game/running.ts @@ -73,7 +73,7 @@ export function composeBody( return { payload, hash } } -export default async function create( +export default async function running( req: NextApiRequest, res: NextApiResponse ) { diff --git a/leaky-ships/pages/api/ws2.ts b/leaky-ships/pages/api/ws.ts similarity index 60% rename from leaky-ships/pages/api/ws2.ts rename to leaky-ships/pages/api/ws.ts index a32c5c7..691b342 100644 --- a/leaky-ships/pages/api/ws2.ts +++ b/leaky-ships/pages/api/ws.ts @@ -3,6 +3,7 @@ import { cServer, } from "../../interfaces/NextApiSocket" import { composeBody, getAnyRunningGame } from "./game/running" +import logging from "@lib/backend/logging" import prisma from "@lib/prisma" import colors from "colors" import { NextApiRequest } from "next" @@ -16,11 +17,11 @@ const SocketHandler = async ( res: NextApiResponseWithSocket ) => { if (res.socket.server.io) { - console.log("Socket is already running " + req.url) + logging("Socket is already running " + req.url, ["infoCyan"], req) } else { - console.log("Socket is initializing " + req.url) + logging("Socket is initializing " + req.url, ["infoCyan"], req) const io: cServer = new Server(res.socket.server, { - path: "/api/ws2", + path: "/api/ws", cors: { origin: "https://leaky-ships.mal-noh.de", }, @@ -41,23 +42,35 @@ const SocketHandler = async ( }) next() } catch (err) { - console.log("Unauthorized") + logging("Unauthorized", ["warn"], socket.request) next(new Error("Unauthorized")) } }) io.on("connection", (socket) => { - console.log( - `User connected <${socket.data.user?.email}>`.green, - socket.id + logging( + `User connected <${socket.data.user?.email}>`.green + + ", " + + socket.id.cyan, + ["infoGreen"], + socket.request ) socket.on("join", async (userId, cb) => { - console.log(socket.rooms, "join") + logging( + "Join: " + JSON.stringify(Array.from(socket.rooms)), + ["debug"], + socket.request + ) const game = await getAnyRunningGame(userId ?? "") if (!game) { - const warst = socket.emit("forbidden") - console.log("forbidden", warst) + socket.emit("forbidden") + logging( + "Forbidden, no game found: " + + JSON.stringify(Array.from(socket.rooms)), + ["debug"], + socket.request + ) return } cb() @@ -68,12 +81,16 @@ const SocketHandler = async ( }) socket.on("disconnecting", () => { - console.log("disconnecting", socket.rooms) // the Set contains at least the socket ID + logging( + "Disconnecting: " + JSON.stringify(Array.from(socket.rooms)), + ["debug"], + socket.request + ) // the Set contains at least the socket ID }) socket.on("disconnect", () => { // socket.rooms.size === 0 - console.log("disconnect", socket.id) + logging("Disconnect: " + socket.id, ["debug"], socket.request) }) }) } diff --git a/leaky-ships/pages/game/index.tsx b/leaky-ships/pages/game.tsx similarity index 55% rename from leaky-ships/pages/game/index.tsx rename to leaky-ships/pages/game.tsx index dcba50e..181b465 100644 --- a/leaky-ships/pages/game/index.tsx +++ b/leaky-ships/pages/game.tsx @@ -1,21 +1,15 @@ -import { authOptions } from "../api/auth/[...nextauth]" -import { getAnyRunningGame } from "../api/game/running" -import { GetServerSideProps } from "next" -import { Session, getServerSession } from "next-auth" +import useGameState from "@lib/hooks/useGameState" import { useRouter } from "next/router" import React, { useEffect } from "react" import { toast } from "react-toastify" -interface Props { - gameId: string - session: Session | null -} - -export default function Game({ gameId, session }: Props) { +export default function Game() { + const { gameProps, session } = useGameState() const router = useRouter() useEffect(() => { - const path = gameId ? "/game/" + gameId : "/start" + const gameId = gameProps.payload?.game?.id + const path = gameId ? "/game" : "/start" toast.promise(router.push(path), { pending: { render: "Wird weitergeleitet...", @@ -45,16 +39,3 @@ export default function Game({ gameId, session }: Props) { ) } - -export const getServerSideProps: GetServerSideProps = async ( - context -) => { - const session = await getServerSession(context.req, context.res, authOptions) - let gameId = "" - if (session?.user.id) { - const game = await getAnyRunningGame(session?.user.id) - if (game && game.state === "running") gameId = game?.id - } - - return { props: { gameId, session } } -} diff --git a/leaky-ships/pages/lobby/[gameId].tsx b/leaky-ships/pages/lobby.tsx similarity index 51% rename from leaky-ships/pages/lobby/[gameId].tsx rename to leaky-ships/pages/lobby.tsx index 75f0d9d..eaf01d7 100644 --- a/leaky-ships/pages/lobby/[gameId].tsx +++ b/leaky-ships/pages/lobby.tsx @@ -1,17 +1,14 @@ -import BurgerMenu from "../../components/BurgerMenu" -import LobbyFrame from "../../components/Lobby/LobbyFrame" -import Settings from "../../components/Lobby/SettingsFrame/Settings" -import Logo from "../../components/Logo" -import { composeBody, getAnyGame } from "../api/game/running" +import BurgerMenu from "../components/BurgerMenu" +import LobbyFrame from "../components/Lobby/LobbyFrame" +import Settings from "../components/Lobby/SettingsFrame/Settings" +import Logo from "../components/Logo" import useGameState from "@lib/hooks/useGameState" -import { GamePropsSchema } from "@lib/zodSchemas" import classNames from "classnames" -import { GetServerSideProps } from "next" import Head from "next/head" import { useState } from "react" -export default function Home(props: GamePropsSchema) { - useGameState(props) +export default function Lobby() { + useGameState() const [settings, setSettings] = useState(false) return ( @@ -36,22 +33,3 @@ export default function Home(props: GamePropsSchema) { ) } - -export const getServerSideProps: GetServerSideProps = async ( - context -) => { - const { gameId } = context.query - - const gameIdString = Array.isArray(gameId) ? gameId[0] : gameId - const game = await getAnyGame(gameIdString ?? "") - if (!game) - return { - redirect: { - destination: "/start", - permanent: false, - }, - } - const body = composeBody(game) - - return { props: body } -} diff --git a/leaky-ships/pages/lobby/index.tsx b/leaky-ships/pages/lobby/index.tsx deleted file mode 100644 index 29fe8b7..0000000 --- a/leaky-ships/pages/lobby/index.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { authOptions } from "../api/auth/[...nextauth]" -import { getAnyRunningGame } from "../api/game/running" -import { GetServerSideProps } from "next" -import { Session, getServerSession } from "next-auth" -import { useRouter } from "next/router" -import React, { useEffect } from "react" -import { toast } from "react-toastify" - -interface Props { - gameId: string - session: Session | null -} - -export default function Lobby({ gameId, session }: Props) { - const router = useRouter() - - useEffect(() => { - const path = gameId ? "/lobby/" + gameId : "/start" - toast.promise(router.push(path), { - pending: { - render: "Wird weitergeleitet...", - toastId: "redirect", - }, - success: { - render: gameId - ? "Spiel gefunden!" - : session?.user - ? "Kein laufendes Spiel." - : "Kein laufendes Spiel. Bitte anmelden.", - toastId: session?.user ? "postRedirect" : "user", - theme: session?.user ? "dark" : undefined, - type: gameId ? "success" : "info", - }, - error: { - render: "Es ist ein Fehler aufgetreten 🤯", - type: "error", - theme: "colored", - }, - }) - }) - - return ( -

-
-
- ) -} - -export const getServerSideProps: GetServerSideProps = async ( - context -) => { - const session = await getServerSession(context.req, context.res, authOptions) - let gameId = "" - if (session?.user.id) { - const game = await getAnyRunningGame(session?.user.id) - if (game && game.state === "launching") gameId = game?.id - } - - return { props: { gameId, session } } -} diff --git a/leaky-ships/pages/start.tsx b/leaky-ships/pages/start.tsx index cb2dd02..9b4b3a9 100644 --- a/leaky-ships/pages/start.tsx +++ b/leaky-ships/pages/start.tsx @@ -1,25 +1,17 @@ import BurgerMenu from "../components/BurgerMenu" import Logo from "../components/Logo" import OptionButton from "../components/OptionButton" -import { authOptions } from "./api/auth/[...nextauth]" import { faEye, faLeftLong } from "@fortawesome/pro-regular-svg-icons" import { faPlus, faUserPlus } from "@fortawesome/pro-solid-svg-icons" import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" import useGameState from "@lib/hooks/useGameState" import { GamePropsSchema } from "@lib/zodSchemas" import status from "http-status" -import { GetServerSideProps } from "next" -import { Session, getServerSession } from "next-auth" import { useRouter } from "next/router" import { useCallback, useEffect, useMemo, useState } from "react" import OtpInput from "react-otp-input" import { toast } from "react-toastify" -interface Props { - q: string | string[] | undefined - session: Session | null -} - function isInputOnlyNumbers(input: string) { return /^\d+$/.test(input) } @@ -51,16 +43,21 @@ const handleConfirmation = () => { ) } -export default function Start({ q, session: initSession }: Props) { +export default function Start() { const [otp, setOtp] = useState("") - const { gameProps, setGameProps } = useGameState() + const { session, setGameProps } = useGameState() const router = useRouter() - const { session: sessionUsed } = useGameState() - const session = useMemo( - () => (sessionUsed ? sessionUsed : initSession), - [sessionUsed, initSession] - ) + const query = useMemo((): { join?: boolean; watch?: boolean } => { + switch (router.query.q) { + case "join": + return { join: true } + case "watch": + return { watch: true } + default: + return {} + } + }, [router]) const gameFetch = useCallback( async (pin?: string) => { @@ -71,25 +68,29 @@ export default function Start({ q, session: initSession }: Props) { .then(isAuthenticated) .then((game) => GamePropsSchema.parse(game)) + const toastId = !pin ? "erstellt" : "angefragt" const res = await toast.promise(gamePromise, { pending: { - render: "Raum wird " + (!pin ? "erstellt" : "angefragt"), + render: "Raum wird " + toastId, + toastId, }, success: { render: "Raum " + (!pin ? "erstellt" : "angefragt") + " 👌", type: "info", theme: "colored", + toastId, }, error: { render: "Es ist ein Fehler aufgetreten 🤯", type: "error", theme: "colored", + toastId, }, }) setGameProps(res) - await toast.promise(router.push("/lobby/" + res.payload.game?.id), { + await toast.promise(router.push("/lobby"), { pending: { render: "Raum wird beigetreten", }, @@ -150,7 +151,7 @@ export default function Start({ q, session: initSession }: Props) { icon={faUserPlus} disabled={!session} > - {q === "join" && session?.user ? ( + {query.join && session?.user ? ( - {q === "watch" && session?.user ? ( + {query.watch && session?.user ? ( ) } - -export const getServerSideProps: GetServerSideProps = async ( - context -) => { - const session = await getServerSession(context.req, context.res, authOptions) - const { q } = context.query - - return { props: { q, session } } -}