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
|
|
@ -1,6 +1,13 @@
|
|||
import { createFileRoute, Outlet, redirect } from "@tanstack/react-router";
|
||||
import { WsProvider } from "../lib/ws-provider.js";
|
||||
import { useEffect } from "react";
|
||||
import { authClient } from "../lib/auth-client.js";
|
||||
import { WsProvider } from "../lib/ws-provider.js";
|
||||
import { useWsConnect } from "../lib/ws-hooks.js";
|
||||
|
||||
const wsBaseUrl =
|
||||
(import.meta.env["VITE_WS_URL"] as string) ||
|
||||
(import.meta.env["VITE_API_URL"] as string) ||
|
||||
"";
|
||||
|
||||
export const Route = createFileRoute("/multiplayer")({
|
||||
component: MultiplayerLayout,
|
||||
|
|
@ -13,9 +20,23 @@ export const Route = createFileRoute("/multiplayer")({
|
|||
},
|
||||
});
|
||||
|
||||
function WsConnector() {
|
||||
const connect = useWsConnect();
|
||||
|
||||
useEffect(() => {
|
||||
void connect(wsBaseUrl).catch((err) => {
|
||||
console.error("WebSocket connection failed:", err);
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function MultiplayerLayout() {
|
||||
return (
|
||||
<WsProvider>
|
||||
<WsConnector />
|
||||
<Outlet />
|
||||
</WsProvider>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue