102 lines
3.1 KiB
JavaScript
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;
|
|
}
|