feat: multiplayer slice — end to end working
WebSocket server: - WS auth via Better Auth session on upgrade request - Router with discriminated union dispatch and two-layer error handling - In-memory connections map with broadcastToLobby - Lobby handlers: join, leave, start - Game handlers: answer, resolve round, end game, game:ready for state sync - Shared game state store (LobbyGameStore interface + InMemory impl) - Timer map separate from store for Valkey-readiness REST API: - POST /api/v1/lobbies — create lobby + add host as first player - POST /api/v1/lobbies/:code/join — atomic join with capacity/status checks - getLobbyWithPlayers added to model for id-based lookup Frontend: - WsClient class with typed on/off, connect/disconnect, isConnected - WsProvider owns connection lifecycle (connect/disconnect/isConnected state) - WsConnector component triggers connection at multiplayer layout mount - Lobby waiting room: live player list, copyable code, host Start button - Game view: reuses QuestionCard, game:ready on mount, round results - MultiplayerScoreScreen: sorted scores, winner highlight, tie handling - Vite proxy: /ws and /api proxied to localhost:3000 for dev cookie fix Tests: - lobbyService.test.ts: create, join, retry, idempotency, full lobby - auth.test.ts: 401 reject, upgrade success, 500 on error - router.test.ts: dispatch all message types, error handling - vitest.config.ts: exclude dist folder Fixes: - server.ts: server.listen() instead of app.listen() for WS support - StrictMode removed from main.tsx (incompatible with WS lifecycle) - getLobbyWithPlayers(id) added for handleLobbyStart lookup
This commit is contained in:
parent
540155788a
commit
8aaafea3fc
13 changed files with 545 additions and 78 deletions
|
|
@ -7,7 +7,46 @@ type ErrorResponse = { success: false; error: string };
|
|||
type GameStartResponse = SuccessResponse<GameSession>;
|
||||
type GameAnswerResponse = SuccessResponse<AnswerResult>;
|
||||
|
||||
vi.mock("@lila/db", () => ({ getGameTerms: vi.fn(), getDistractors: vi.fn() }));
|
||||
vi.mock("@lila/db", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@lila/db")>();
|
||||
return { ...actual, getGameTerms: vi.fn(), getDistractors: vi.fn() };
|
||||
});
|
||||
|
||||
vi.mock("../lib/auth.js", () => ({
|
||||
auth: {
|
||||
api: {
|
||||
getSession: vi
|
||||
.fn()
|
||||
.mockResolvedValue({
|
||||
session: {
|
||||
id: "session-1",
|
||||
userId: "user-1",
|
||||
token: "fake-token",
|
||||
expiresAt: new Date(Date.now() + 1000 * 60 * 60),
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
ipAddress: null,
|
||||
userAgent: null,
|
||||
},
|
||||
user: {
|
||||
id: "user-1",
|
||||
name: "Test User",
|
||||
email: "test@test.com",
|
||||
emailVerified: false,
|
||||
image: null,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
handler: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("better-auth/node", () => ({
|
||||
fromNodeHeaders: vi.fn().mockReturnValue({}),
|
||||
toNodeHandler: vi.fn().mockReturnValue(vi.fn()),
|
||||
}));
|
||||
|
||||
import { getGameTerms, getDistractors } from "@lila/db";
|
||||
import { createApp } from "../app.js";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue