// This is based on PubSubCore // https://github.com/PeterScott/pubsubcore/blob/master/pubsubcore.js // PubSubCore: Simple pub/sub library for Node.js and Socket.IO const util = require('util') const sets = require('simplesets') const io = require('socket.io') const net = require('net') /// /////////////////////////// // Tracking who's in what room /// /////////////////////////// // Dict mapping room names with people to sets of client objects. const rooms = {} // Dict mapping room names with people to sets of usernames. const room_users = {} // Dict mapping sids to sets of rooms. const sid_rooms = {} // Add a client to a room and return the sid:client mapping. exports.add_to_room = function(client, room, callback) { // console.log('Client ' + client.username + ' (' + client.id + ') added to room ' + room); if (!(sid_rooms.hasOwnProperty(client.id))) sid_rooms[client.id] = new sets.Set() sid_rooms[client.id].add(room) if (!(rooms.hasOwnProperty(room))) rooms[room] = new sets.Set() rooms[room].add(client) if (!(room_users.hasOwnProperty(room))) room_users[room] = new sets.Set() room_users[room].add(client.username) callback(rooms[room].array()) } // Remove a client from all rooms and return the username:client // mapping for everybody in those rooms. exports.remove_from_all_rooms = function(client, callback) { const affected_clients = new sets.Set() if (sid_rooms.hasOwnProperty(client.id)) { const client_rooms = sid_rooms[client.id].array() for (let i = 0; i < client_rooms.length; i++) { const room = client_rooms[i] if (rooms.hasOwnProperty(room)) { rooms[room].remove(client) if (rooms[room].size() === 0) { delete rooms[room] } } if (room_users.hasOwnProperty(room)) { room_users[room].remove(client.username) if (room_users[room].size() === 0) { delete room_users[room] } } if (rooms.hasOwnProperty(room)) { const this_room = rooms[room].array() for (let j = 0; j < this_room.length; j++) { affected_clients.add(this_room[j]) } } } } // console.log('Client ' + client.username + ' (' + client.id + ') disconnected.'); delete sid_rooms[client.id] callback(affected_clients.array()) } // Remove a client from a room and return the username:client mapping // for everybody in that room. Returns [] if the room does not exist, // or if the client was not in the room to begin with. function remove_from_room(client, room, callback) { if (!rooms.hasOwnProperty(room) || !rooms[room].has(client)) { callback([]) return } // Delete from the room rooms[room].remove(client) if (rooms[room].size() === 0) { delete rooms[room] } if (room_users.hasOwnProperty(room)) { room_users[room].remove(client.username) if (room_users[room].size() === 0) { delete room_users[room] } } callback(exports.room_clients(room)) } // Return list of clients in the given room. exports.room_clients = function(room) { return rooms.hasOwnProperty(room) ? rooms[room].array() : [] } // Return true if room contains the given client, false otherwise. exports.client_in_room = function(room, client) { return rooms.hasOwnProperty(room) && rooms[room].has(client) } // Return list of usernames in given room exports.users_in_room = function(room) { return room_users.hasOwnProperty(room) ? room_users[room].array() : [] } // Return list of usernames in given room exports.room_clients_other_than_me = function(room, client) { if (rooms.hasOwnProperty(room)) { const clients = rooms[room] // console.dir(clients.array()); clients.remove(client) // console.dir(clients.array()); return clients.array() } return [] } // gets the current room of the client (assumes one room -- will select first one if in multiple) exports.get_room = function(client) { let client_rooms = null if (sid_rooms.hasOwnProperty(client.id)) { client_rooms = sid_rooms[client.id].array() } if (client_rooms !== null) { return client_rooms[0] } return null } // Generic server code exports.add_to_room_and_announce = function(client, room, msg) { // Add user info to the current dramatis personae exports.add_to_room(client, room, (clients) => { // Broadcast new-user notification for (let i = 0; i < clients.length; i++) { if (clients[i].id != client.id) { clients[i].json.send(msg) } } }) } /* exports.on_leave_room = function (client, room) { remove_from_room(client, room, function(clients) { console.log(client + ' disconnected, yo'); console.log(clients); for (var i = 0; i < clients.length; i++) clients[i].send({ announcement: true, name: client.username || 'anonymous', action: 'disconnected' }); }); } */ // remember that this announces to all rooms that this client was a member of exports.remove_from_all_rooms_and_announce = function(client, msg) { exports.remove_from_all_rooms(client, (clients) => { for (let i = 0; i < clients.length; i++) { if (clients[i].id != client.id) { clients[i].json.send(msg) } } }) } /// /////////////////////////// // Broadcasting functions /// /////////////////////////// // Broadcast message to all clients exports.broadcast = function(msg) { if (socket) socket.broadcast(msg) net_server_streams.each((stream) => { stream.write(`${JSON.stringify(msg)}\r\n`) }) } // Broadcast message to all clients in a given room. exports.broadcast_room = function(room, msg) { const clients = exports.room_clients(room) for (let i = 0; i < clients.length; i++) { clients[i].json.send(msg) } } // Broadcast message to all the other clients that are in rooms with this client exports.broadcast_to_roommates = function(client, msg) { let roommates = new sets.Set() if (sid_rooms.hasOwnProperty(client.id)) { const client_rooms = sid_rooms[client.id].array() for (let i = 0; i < client_rooms.length; i++) { const room = client_rooms[i] if (rooms.hasOwnProperty(room)) { const this_room = rooms[room].array() for (let j = 0; j < this_room.length; j++) { roommates.add(this_room[j]) } } } } // remove self from the set roommates.remove(client) roommates = roommates.array() // console.log('client: ' + client.id + " is broadcasting to: "); for (let k = 0; k < roommates.length; k++) { // console.log(' - ' + roommates[i].id); roommates[k].json.send(msg) } }