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