import { Server as SocketIOServer } from 'socket.io'; import cookie from 'cookie'; import config from '../config.js'; import { findSessionUser } from '../auth/service.js'; import { lobby } from './lobby.js'; import { getGame, listGames } from './gameRegistry.js'; function broadcastRoom(io, room) { io.to(`room:${room.id}`).emit('room:update', room); } function broadcastLobby(io, gameSlug) { io.to(`lobby:${gameSlug}`).emit('lobby:update', lobby.list(gameSlug)); } export function attachMultiplayer(httpServer) { const io = new SocketIOServer(httpServer, { cors: { origin: config.socket.corsOrigin, credentials: true }, }); io.use((socket, next) => { const raw = socket.handshake.headers.cookie ?? ''; const cookies = cookie.parse(raw || ''); const sid = cookies[config.auth.cookieName]; const user = findSessionUser(sid); if (!user) return next(new Error('Not authenticated.')); socket.data.user = user; next(); }); io.on('connection', (socket) => { const { user } = socket.data; socket.emit('hello', { user, games: listGames() }); socket.on('lobby:subscribe', (gameSlug) => { if (!getGame(gameSlug)) return; socket.join(`lobby:${gameSlug}`); socket.emit('lobby:update', lobby.list(gameSlug)); }); socket.on('lobby:unsubscribe', (gameSlug) => { socket.leave(`lobby:${gameSlug}`); }); socket.on('room:create', ({ gameSlug, name }, ack) => { try { const room = lobby.create({ gameSlug, hostUser: user, name }); socket.join(`room:${room.id}`); broadcastLobby(io, gameSlug); ack?.({ ok: true, room }); } catch (err) { ack?.({ ok: false, error: err.message }); } }); socket.on('room:join', ({ roomId }, ack) => { try { const room = lobby.join(roomId, user); socket.join(`room:${room.id}`); broadcastRoom(io, room); broadcastLobby(io, room.gameSlug); ack?.({ ok: true, room }); } catch (err) { ack?.({ ok: false, error: err.message }); } }); socket.on('room:leave', ({ roomId }, ack) => { const before = lobby.get(roomId); const room = lobby.leave(roomId, user.id); socket.leave(`room:${roomId}`); if (room) broadcastRoom(io, room); if (before) broadcastLobby(io, before.gameSlug); ack?.({ ok: true }); }); socket.on('room:message', ({ roomId, payload }) => { const room = lobby.get(roomId); if (!room || !room.players.some((p) => p.userId === user.id)) return; io.to(`room:${roomId}`).emit('room:message', { from: { id: user.id, username: user.username }, payload, at: Date.now(), }); }); socket.on('disconnect', () => { // Drop the user from any rooms they were in. for (const room of [...lobby.rooms.values()]) { if (room.players.some((p) => p.userId === user.id)) { const updated = lobby.leave(room.id, user.id); if (updated) broadcastRoom(io, updated); broadcastLobby(io, room.gameSlug); } } }); }); return io; }