import { randomUUID } from 'node:crypto'; import { BaseHandler } from 'redweb'; import type { MessageOf, RedWebSocket, SocketMessageFrame } from 'redweb'; import { SCHEMAS } from './schema'; class Player { readonly session = randomUUID(); x = 0; y = 0; constructor(readonly name: string) {} } function requireUnjoined(socket: RedWebSocket) { if (socket.context?.session) throw new Error('Already joined.'); } function currentPlayer(socket: RedWebSocket) { const session = socket.context?.session as { data?: unknown } | null | undefined; if (!(session?.data instanceof Player)) throw new Error('Join or resume first.'); return session.data; } export class Join extends BaseHandler { constructor() { super(SCHEMAS.join); } override handle(socket: RedWebSocket, { name }: MessageOf, frame?: SocketMessageFrame) { requireUnjoined(socket); const player = new Player(name); if (!socket.createSession?.(player.session, player)) throw new Error('Session capacity reached.'); return socket.sendEvent?.('state', player, { requestId: frame?.requestId }); } } export class Move extends BaseHandler { constructor() { super(SCHEMAS.move); } override handle(socket: RedWebSocket, { x, y }: MessageOf, frame?: SocketMessageFrame) { const player = currentPlayer(socket); player.x = x; player.y = y; return socket.sendEvent?.('state', player, { requestId: frame?.requestId }); } } export class Resume extends BaseHandler { constructor() { super(SCHEMAS.resume); } override handle(socket: RedWebSocket, { session }: MessageOf, frame?: SocketMessageFrame) { requireUnjoined(socket); if (!(socket.resumeSession?.(session) instanceof Player)) throw new Error('Session expired or unknown.'); return socket.sendEvent?.('state', currentPlayer(socket), { requestId: frame?.requestId }); } }