102 lines
No EOL
2.6 KiB
TypeScript
102 lines
No EOL
2.6 KiB
TypeScript
import { requireAuthentication, t } from "./base"
|
|
import z from "zod"
|
|
import { observable } from "@trpc/server/observable"
|
|
import type { GameAction } from "../../shared/game/actions"
|
|
import { isDev } from "../isDev"
|
|
import { gameRouter } from "./game"
|
|
import { nanoid } from "nanoid"
|
|
import { createGame, getGameByLobbyCode } from "../game"
|
|
import type { GameEvent } from "../../shared/game/events"
|
|
import { type User, usersById, usersByToken } from "../user"
|
|
|
|
export const appRouter = t.router({
|
|
game: gameRouter,
|
|
|
|
login: t.procedure
|
|
.input(z.object({
|
|
name: z.string().min(1).max(20)
|
|
}))
|
|
.mutation(async ({ input, ctx }) => {
|
|
if (ctx.user !== null) {
|
|
usersById.delete(ctx.user.id)
|
|
usersByToken.delete(ctx.user.token)
|
|
}
|
|
|
|
const newUser: User = {
|
|
id: nanoid(16),
|
|
token: nanoid(64),
|
|
name: input.name
|
|
}
|
|
|
|
usersById.set(newUser.id, newUser)
|
|
usersByToken.set(newUser.token, newUser)
|
|
|
|
ctx.res!.cookie("token", newUser.token, {
|
|
maxAge: 60 * 60 * 24 * 365,
|
|
httpOnly: true,
|
|
secure: !isDev,
|
|
sameSite: "strict"
|
|
})
|
|
|
|
return {
|
|
id: newUser.id
|
|
}
|
|
}),
|
|
|
|
getSelf: t.procedure
|
|
.query(async ({ ctx }) => {
|
|
if (ctx.user === null) return { user: null }
|
|
|
|
return {
|
|
user: ctx.user
|
|
}
|
|
}),
|
|
|
|
createGame: t.procedure
|
|
.use(requireAuthentication)
|
|
.mutation(async ({}) => {
|
|
const game = createGame()
|
|
|
|
return {
|
|
lobbyCode: game.lobbyCode
|
|
}
|
|
}),
|
|
|
|
join: t.procedure
|
|
.use(requireAuthentication)
|
|
.input(z.object({
|
|
lobbyCode: z.string().nonempty()
|
|
}))
|
|
.subscription(async ({ input, ctx }) => {
|
|
const game = getGameByLobbyCode(input.lobbyCode)
|
|
if (game === null) throw new Error("There is no game with this code.")
|
|
|
|
game.addPlayer(ctx.user.id, ctx.user.name)
|
|
|
|
return observable<GameEvent>(emit => {
|
|
const handleBroadcastAction = (action: GameAction) => emit.next({ type: "action", action })
|
|
const handleNewRound = () => emit.next({ type: "new_round" })
|
|
const handleDestroyed = () => setTimeout(() => emit.complete(), 500)
|
|
|
|
const handlePrivateAction = (playerId: string, action: GameAction) => {
|
|
if (playerId === ctx.user.id) emit.next({ type: "action", action })
|
|
}
|
|
|
|
game.on("broadcast_action", handleBroadcastAction)
|
|
game.on("private_action", handlePrivateAction)
|
|
game.on("new_round", handleNewRound)
|
|
game.on("destroyed", handleDestroyed)
|
|
|
|
game.sendAllOldActionsTo(ctx.user.id)
|
|
|
|
return () => {
|
|
game.off("broadcast_action", handleBroadcastAction)
|
|
game.off("private_action", handlePrivateAction)
|
|
game.off("new_round", handleNewRound)
|
|
game.off("destroyed", handleDestroyed)
|
|
}
|
|
})
|
|
})
|
|
})
|
|
|
|
export type AppRouter = typeof appRouter |