twenty-one/backend/trpc/index.ts

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