Some after work from last commit
This commit is contained in:
parent
30db96a3f7
commit
12295b316f
14 changed files with 130 additions and 176 deletions
|
@ -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}> </Fragment>
|
||||
))}
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
})
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
) {
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -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 } }
|
||||
}
|
|
@ -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 }
|
||||
}
|
|
@ -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 } }
|
||||
}
|
|
@ -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 } }
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue