📄 server.js
import { createServer } from 'http';
import { Server } from 'socket.io';
import { nanoid } from 'nanoid';

const httpServer = createServer();
const io = new Server(httpServer, {
	cors: {
		origin: '*',
		methods: ['GET', 'POST']
	}
});

// Room state management
const rooms = new Map();
// Map of socket IDs to room codes
const socketToRoom = new Map();

// Generate a unique 6-character room code
function generateRoomCode() {
	let code;
	do {
		code = nanoid(6).toUpperCase();
	} while (rooms.has(code));
	return code;
}

io.on('connection', (socket) => {
	console.log('Client connected:', socket.id);

	// Master creates a room
	socket.on('create-room', (callback) => {
		const roomCode = generateRoomCode();
		rooms.set(roomCode, {
			master: socket.id,
			displays: [],
			createdAt: Date.now()
		});
		socketToRoom.set(socket.id, roomCode);
		socket.join(roomCode);

		console.log(`Room created: ${roomCode} by ${socket.id}`);
		callback({ success: true, roomCode });
	});

	// Display device joins a room
	socket.on('join-room', (roomCode, callback) => {
		const room = rooms.get(roomCode);

		if (!room) {
			callback({ success: false, error: 'Room not found' });
			return;
		}

		// Add display to room
		room.displays.push(socket.id);
		socketToRoom.set(socket.id, roomCode);
		socket.join(roomCode);

		// Notify master of new display
		io.to(room.master).emit('display-joined', {
			displayId: socket.id,
			totalDisplays: room.displays.length
		});

		console.log(`Display ${socket.id} joined room ${roomCode}`);
		callback({ success: true, masterId: room.master });
	});

	// Game commands: master sends to specific display
	socket.on('game-command', ({ to, command }) => {
		io.to(to).emit('game-command', command);
	});

	// Game broadcast: master sends to all displays in room
	socket.on('game-broadcast', ({ roomCode, command }) => {
		const room = rooms.get(roomCode);
		if (room) {
			room.displays.forEach(displayId => {
				io.to(displayId).emit('game-command', command);
			});
		}
	});

	// Display actions: display sends to master
	socket.on('display-action', (action) => {
		const roomCode = socketToRoom.get(socket.id);
		if (roomCode) {
			const room = rooms.get(roomCode);
			if (room) {
				io.to(room.master).emit('display-action', {
					from: socket.id,
					action
				});
			}
		}
	});

	// Handle disconnections
	socket.on('disconnect', () => {
		console.log('Client disconnected:', socket.id);
		const roomCode = socketToRoom.get(socket.id);

		if (roomCode) {
			const room = rooms.get(roomCode);
			if (room) {
				// If master disconnects, close the room
				if (room.master === socket.id) {
					console.log(`Master disconnected, closing room ${roomCode}`);
					// Notify all displays
					room.displays.forEach(displayId => {
						io.to(displayId).emit('room-closed');
						socketToRoom.delete(displayId);
					});
					rooms.delete(roomCode);
				} else {
					// Remove display from room
					room.displays = room.displays.filter(id => id !== socket.id);
					// Notify master
					io.to(room.master).emit('display-left', {
						displayId: socket.id,
						totalDisplays: room.displays.length
					});
				}
			}
			socketToRoom.delete(socket.id);
		}
	});
});

const PORT = process.env.PORT || 3001;
httpServer.listen(PORT, () => {
	console.log(`Signaling server running on port ${PORT}`);
});