feat(api): add WebSocket foundation and multiplayer game store
- Add ws/ directory: server setup, auth, router, connections map - WebSocket auth rejects upgrade with 401 if no Better Auth session - Router parses WsClientMessageSchema, dispatches to handlers, two-layer error handling (AppError -> WsErrorSchema, unknown -> 500) - connections.ts: in-memory Map<lobbyId, Map<userId, WebSocket>> with addConnection, removeConnection, broadcastToLobby - LobbyGameStore interface + InMemoryLobbyGameStore implementation following existing GameSessionStore pattern - multiplayerGameService: generateMultiplayerQuestions() decoupled from single-player flow, hardcoded defaults en->it nouns easy 3 rounds - handleLobbyJoin and handleLobbyLeave implemented - WsErrorSchema added to shared schemas - server.ts switched to createServer + setupWebSocket
This commit is contained in:
parent
b0aef8cc16
commit
745c5c4e3a
14 changed files with 443 additions and 1 deletions
44
apps/api/src/ws/connections.ts
Normal file
44
apps/api/src/ws/connections.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import type { WebSocket } from "ws";
|
||||
|
||||
// Map<lobbyId, Map<userId, WebSocket>>
|
||||
const connections = new Map<string, Map<string, WebSocket>>();
|
||||
|
||||
export const addConnection = (
|
||||
lobbyId: string,
|
||||
userId: string,
|
||||
ws: WebSocket,
|
||||
): void => {
|
||||
if (!connections.has(lobbyId)) {
|
||||
connections.set(lobbyId, new Map());
|
||||
}
|
||||
connections.get(lobbyId)!.set(userId, ws);
|
||||
};
|
||||
|
||||
export const removeConnection = (lobbyId: string, userId: string): void => {
|
||||
const lobby = connections.get(lobbyId);
|
||||
if (!lobby) return;
|
||||
lobby.delete(userId);
|
||||
if (lobby.size === 0) {
|
||||
connections.delete(lobbyId);
|
||||
}
|
||||
};
|
||||
|
||||
export const getConnections = (lobbyId: string): Map<string, WebSocket> => {
|
||||
return connections.get(lobbyId) ?? new Map();
|
||||
};
|
||||
|
||||
export const broadcastToLobby = (
|
||||
lobbyId: string,
|
||||
message: unknown,
|
||||
excludeUserId?: string,
|
||||
): void => {
|
||||
const lobby = connections.get(lobbyId);
|
||||
if (!lobby) return;
|
||||
const payload = JSON.stringify(message);
|
||||
for (const [userId, ws] of lobby) {
|
||||
if (excludeUserId && userId === excludeUserId) continue;
|
||||
if (ws.readyState === ws.OPEN) {
|
||||
ws.send(payload);
|
||||
}
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue