fertig-classic-games/server/multiplayer/index.js

102 lines
3.1 KiB
JavaScript

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;
}