Some after work from last commit

This commit is contained in:
aronmal 2023-04-27 18:57:00 +02:00
parent 30db96a3f7
commit 12295b316f
Signed by: aronmal
GPG key ID: 816B7707426FC612
14 changed files with 130 additions and 176 deletions

View file

@ -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 }) {
<Icon src="speech_bubble.png">Chat</Icon>
<h1 className="font-farro text-5xl font-medium">
Game-PIN:{" "}
<span className="underline">{payload?.gamePin?.pin ?? "---"}</span>
{isConnected ? (
<span className="underline">{payload?.gamePin?.pin ?? "---"}</span>
) : (
<FontAwesomeIcon icon={faSpinnerThird} spin={true} />
)}
</h1>
<Icon src="gear.png" onClick={openSettings}>
Settings
@ -42,7 +65,8 @@ function LobbyFrame({ openSettings }: { openSettings: () => void }) {
/>
) : (
<p className="font-farro w-96 text-center text-4xl font-medium">
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) => (
<Fragment key={i}>&nbsp;</Fragment>
))}

View file

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

View file

@ -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<SetStateAction<GamePropsSchema>>
setGameProps: Dispatch<GamePropsSchema>
}
export const gameContext = createContext<gameContext>({
@ -24,8 +23,30 @@ export const gameContext = createContext<gameContext>({
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<GamePropsSchema>(initialValue)
const [gameProps, setGameProps] = useReducer(updateProps, initialValue)
const value = useMemo<gameContext>(
() => ({ gameProps, setGameProps }),
@ -35,15 +56,10 @@ export function GameContextProvider({ children }: { children: ReactNode }) {
return <gameContext.Provider value={value}>{children}</gameContext.Provider>
}
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)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -73,7 +73,7 @@ export function composeBody(
return { payload, hash }
}
export default async function create(
export default async function running(
req: NextApiRequest,
res: NextApiResponse<GamePropsSchema>
) {

View file

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

View file

@ -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) {
</div>
)
}
export const getServerSideProps: GetServerSideProps<Props> = 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 } }
}

View file

@ -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) {
</div>
)
}
export const getServerSideProps: GetServerSideProps<GamePropsSchema> = 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 }
}

View file

@ -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 (
<div className="h-full bg-theme">
<div className="mx-auto flex h-full max-w-screen-md flex-col items-center justify-evenly"></div>
</div>
)
}
export const getServerSideProps: GetServerSideProps<Props> = 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 } }
}

View file

@ -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 ? (
<OtpInput
shouldAutoFocus
containerStyle={{ color: "initial" }}
@ -166,7 +167,7 @@ export default function Start({ q, session: initSession }: Props) {
)}
</OptionButton>
<OptionButton icon={faEye}>
{q === "watch" && session?.user ? (
{query.watch && session?.user ? (
<OtpInput
shouldAutoFocus
containerStyle={{ color: "initial" }}
@ -187,12 +188,3 @@ export default function Start({ q, session: initSession }: Props) {
</div>
)
}
export const getServerSideProps: GetServerSideProps<Props> = async (
context
) => {
const session = await getServerSession(context.req, context.res, authOptions)
const { q } = context.query
return { props: { q, session } }
}