talkie/server.js

82 lines
2.4 KiB
JavaScript

const express = require('express');
const { WebSocketServer } = require('ws');
const { randomUUID } = require('crypto');
const path = require('path');
const app = express();
app.use(express.static(path.join(__dirname, 'public')));
const server = app.listen(3000, () => {
console.log('Talkie running on http://localhost:3000');
});
const wss = new WebSocketServer({ server });
// roomId -> Set of { ws, id, name }
const rooms = new Map();
function getRoomMembers(roomId) {
return rooms.get(roomId) ?? new Set();
}
function broadcast(roomId, data, excludeId = null) {
const msg = JSON.stringify(data);
getRoomMembers(roomId).forEach(client => {
if (client.id !== excludeId && client.ws.readyState === 1) {
client.ws.send(msg);
}
});
}
wss.on('connection', (ws) => {
const clientId = randomUUID();
let clientRoom = null;
let clientName = 'Anonimo';
ws.send(JSON.stringify({ type: 'id', id: clientId }));
ws.on('message', (raw) => {
let msg;
try { msg = JSON.parse(raw); } catch { return; }
if (msg.type === 'join') {
clientRoom = msg.room.trim().toUpperCase();
clientName = (msg.name || 'Anonimo').slice(0, 20);
if (!rooms.has(clientRoom)) rooms.set(clientRoom, new Set());
const room = rooms.get(clientRoom);
// Tell new user about existing peers
const peers = [...room].map(c => ({ id: c.id, name: c.name }));
ws.send(JSON.stringify({ type: 'peers', peers }));
// Tell existing peers about the new user
broadcast(clientRoom, { type: 'peer-joined', id: clientId, name: clientName });
room.add({ ws, id: clientId, name: clientName });
}
// WebRTC signaling: forward to specific peer
if (['offer', 'answer', 'ice'].includes(msg.type)) {
const room = getRoomMembers(clientRoom);
const target = [...room].find(c => c.id === msg.to);
if (target?.ws.readyState === 1) {
target.ws.send(JSON.stringify({ ...msg, from: clientId }));
}
}
// Talking state: broadcast to room
if (msg.type === 'talking') {
broadcast(clientRoom, { type: 'talking', id: clientId, name: clientName, active: msg.active }, clientId);
}
});
ws.on('close', () => {
if (!clientRoom) return;
const room = getRoomMembers(clientRoom);
room.forEach(c => { if (c.id === clientId) room.delete(c); });
broadcast(clientRoom, { type: 'peer-left', id: clientId, name: clientName });
if (room.size === 0) rooms.delete(clientRoom);
});
});