Disconnect All Users of Room when "Host" Disconnects in Socket.IO - node.js

Currently I have the originator of a room marked as the "Host".
I need to set it so that if the "Host" clicks a "Close Room" link, it will disconnect all over users from that room id.
How can I grab all users from socket.manager.roomClients or something of the sorts, loop through them all and run some type of socket.leave( room_id ) if the "Host's" room_id matches the key in the socket manager?
Thanks for any insight or help. Let me know if I need to clarify anything.

There doesn't appear to be a mechanism for this in socket.io, but it's not terribly difficult to implement it yourself. You just iterate over each socket in the room and call the disconnect method. This would ideally be a io.sockets level method, but you've got to know the namespace that the socket is using, so I added a prototype at the socket level to boot everyone out of a room. Take a look at the following:
var sio = require('socket.io');
var io = sio.listen(app);
sio.Socket.prototype.disconnectRoom = function (name) {
var nsp = this.namespace.name
, name = (nsp + '/') + name;
var users = this.manager.rooms[name];
for(var i = 0; i < users.length; i++) {
io.sockets.socket(users[i]).disconnect();
}
return this;
};
You can use it as shown below.
socket.on('hostdisconnect', function() {
socket.disconnectRoom('roomName');
});
A word of warning, if you're going to use something like this, it's important to be aware it uses internal data structures which may or may not be around in the next version of socket.io. It's possible for an update from socket.io to break this functionality.

The latest update in socket.io is the leave() method as a companion to join().
Eg;
socket.join('room1');
socket.leave('room1');
May be this help you in your scenario.

Here is how I managed to achieve this in socket.io "version": "2.1.1" :
function disconnectRoom(room: string, namespace = '/') {
io.of(namespace).in(room).clients((err, clients) => {
clients.forEach(clientId => this.io.sockets.connected[clientId].disconnect());
});
}

After much fighting with trying to do it through socket.io I figured out a way to get the room to be killed through Node.JS/Express
app.get('/disconnect', function(req, res){
console.log("Disconnecting Room " + req.session.room_id);
req.session.destroy(function(err){
if (err){
console.log('Error destroying room... ' + err);
} else{
res.redirect('/');
}
});
});

Related

How to emit socket to specific socket connection?

In my node app,
socket.on('test', function (req) {
controller.test(socket, req, 'test');
})
This way I store all users socket connections in server...
var userSockets = []; //Used to store all connected users data.
userSockets[user.id] = socket; // Storing whole socket object.
// E.x: userSockets[14] = socket; // Stored like this.
Function to get all socket data
getUserSocket() {
return userSockets;
}
Now I need to emit to the specific socket, I have tried this but I got an error.
let allUserSocketListData = databaseHelper.getUserSocket();
allUserSocketListData[result.data[0].id].emit('response' , data);
// E.x: allUserSocketListData[14].emit('response' , data);
Error:
.emit() is not a function.
Update
I have one function in that I'm storing all user's socket data.
validateUser(user, socket) {
... // My some code
userSockets[user.id] = socket;
}
Namespaces and rooms were build for that specific reason, but when you need something handy without extra code, sending emits directly can do job.
Here's a quick approach:
1: Your connection event should store the socket ID and not the whole object
let socketIds = []
socketServer.on("connection",(socket)=>{
socketIds.push(socket.id)
socket.emit("message", "hey")
})
2: Now if you want to send something on the first client only for example, you should first check if it's already registered and proceed with the emit.
if (socketServer.sockets.connected.hasOwnProperty(socketIds[0])){
socketServer.sockets.connected[socketIds[0]].emit("message", "hey again")
} else {
console.error("Error: Wrong Id")
}

how to access a child node firebase nodejs

hello I have a data like this in my firebase database
I want to access the sender name.
Here is my code
var ref = firebase.database().ref().child('chat');
ref.on("child_added", function(data, prevChildKey) {
var newPlayer = data.val();
var key = data.key;
console.log(key);
});
data.key gives me the key 1-5. I want to access the value for example which is sender_name under 1-5. I don't know how can I do that. I have done something like this
var newPlayer = data.val();
console.log(newPlayer[key]['sender_name'];
But I got an undefined error
Child elements of chat node are 1-5, 6-10, etc. keys, not chat items, based on the data structure you show.
Firebase child_added event is only fired on direct children.
If you wish to listen to new items with child_added listener, you can:
Listen on each chat/1-5, chat/6-10, etc, for added children, but I understand this is not very convenient
Example to always listen to 1-5 children:
var ref = firebase.database().ref().child('chat/1-5');
ref.on("child_added", function(data) {
var item = data.val();
console.log('New message', item['sender_name'], item.text);
});
And a generic solution if you don't know what is the first key but you know it starts with '1-':
var ref = firebase.database().ref().child('chat');
var firstKey = null
ref.on("child_added", function(data) {
// If first key is what you are looking for
// and changed (or is null) update item listener
if (data.key.startsWith('1-') && data.key !== firstKey) {
// But first remove previous listener if present
firstKey && ref.child(firstKey).off("child_added", checkItemsAdded)
firstKey = data.key
ref.child(firstKey).on("child_added", checkItemsAdded)
}
});
function checkItemsAdded(data) {
var item = data.val();
console.log('New message', item['sender_name'], item.text);
}
And even more generic, listen to all intermediary keys (if you don't know what they are):
var ref = firebase.database().ref().child('chat');
ref.on("child_added", function(data) {
// function checkItemsAdded is the same as above
ref.child(firstKey).on("child_added", checkItemsAdded)
});
Note that this code listens to all sub keys like 1-5, etc. So if there are just a few, or a few dozen, I would say it is OK, but I would not advise to scale this above a few hundreds.
Other (recommended) option:
Change data structure in a way that all items are directly written as chat direct children. (Remove 1-5, 6-10 level)
As #Pandaiolo explains, the child_added events fires for direct children of the location you listen to. For each child it gives you a snapshot of all data under that child. So your data parameter contains all the information you're looking for. You just need to loop over it, to get one level deeper into the JSON with:
var ref = firebase.database().ref().child('chat');
ref.on("child_added", function(data, prevChildKey) {
var newPlayer = data.val();
console.log(data.key);
data.forEach(function(child) {
console.log(child.key, child.child("sender_name").val());
});
});
This will print all sender names.

Wait for multiple events from different modules and then start the server

I'm building an app in which I have 2 different modules.One of them is the connection to mongodb and the other module is the connection to redis for session management. Each module has 2 events in this case error and connect.
Here is an example from one of the modules:
var sessionStore = new SessionStore(app.get("sessionStore"));
sessionStore.client.on("error", function(err) {
console.error("[SessionStore]:", "connection error:", err.address + ":" + err.port, "for process:", process.pid);
var sessionStoreError = new Error("Session store error");
sessionStoreError.message = "Session store connection error";
sessionStoreError.code = err.code;
sessionStoreError.address = err.address;
sessionStoreError.port = err.port;
app.emit("error", sessionStoreError);
});
sessionStore.client.on("connect", function() {
console.log("[SessionStore]:", "connection established:", app.get("sessionStore").host + ":" + app.get("sessionStore"), "for process:", process.pid);
app.emit("ready");
});
Almost same way happens with mongodb module.
What I would like to achieve (avoiding the pyramid of doom) is something like this but when both redis and mongodb connections from their modules is successful:
app.on("ready", function(){
app.listen(app.get("port"));
});
Keep in mind that I could nest each module inside the other and perhaps require sequentialy the modules inside the connect handlers and then emit a final event to app, but this is not elegant I suppose according to my tastes that is.
Is there any elegant way to wait for 2 events and then start the server ?
bootable provides an initialization layer that you can also use for Express apps. This doesn't strictly provide a solution to your question ("wait for 2 events"), but it does solve the issue of waiting for multiple asynchronous modules before starting the server.
You can add boot phases for MongoDB and Redis. Once all phases are ready, the callback to app.boot() is called (you can check for any initialization errors there) and when everything is okay, the app can start listening for incoming connections.
Here's your session store example turned into a boot phase:
module.exports = function(app) {
app.phase(function(done) {
var sessionStore = new SessionStore(app.get("sessionStore"));
sessionStore.client.once("error", function(err) {
console.error("[SessionStore]:", "connection error:", err.address + ":" + err.port, "for process:", process.pid);
var sessionStoreError = new Error("Session store error");
sessionStoreError.message = "Session store connection error";
sessionStoreError.code = err.code;
sessionStoreError.address = err.address;
sessionStoreError.port = err.port;
return done(sessionStoreError);
});
sessionStore.client.once("connect", function() {
console.log("[SessionStore]:", "connection established:", app.get("sessionStore").host + ":" + app.get("sessionStore"), "for process:", process.pid);
return done();
});
});
};
Your main code would look something like this:
var bootable = require('bootable');
var express = require('express')
var app = bootable(express());
require('./boot/session-store')(app); // loads the module above
require('./boot/mongodb')(app); // a MongoDB boot phase
// This is the equivalent of your `app.on('ready', ...)`
app.boot(function(err) {
if (err) throw err;
app.listen(app.get("port"));
});
From the top of my head, Here is the best solution I could think of:
I think the most "elegant" way is, like you suggested, to call each module with async.waterfall, and when both of them are done, send an event to the app. I think this is a good solution but the only problem with it is that you are losing time by activating the module sequentialy and not in the same time.
To save time, maybe you can use a less "elegant" solution like this:
The redis module will emit an event called "redisReady" and the mongoDb module will emit one called "mongoReady", and in the main module there will be:
app.on("redisReady", function(){
redisModuleReady = true;
if (mongoModuleReady) {
app.listen(app.get("port"));
}
});
app.on("mongoReady", function(){
mongoModuleReady = true;
if (redisModuleReady) {
app.listen(app.get("port"));
}
});
This is much less "pretty" but saves time.
Surely there are more solutions, but this is what I came up with.

What should I be using? Socket.io rooms or Redis pub-sub?

Pretty simple question. I am building a realtime game using nodejs as my backend and I am wondering if there is any information available on which one is more reliable and which one is more efficient?
I am heavily using both Redis and Socket.io throughout my code. So I want to know whether I should be utilizing Socket.io's Rooms or I would be better off using redis' pub-sub ?
Update:
Just realized there is a very important reason why you may want to use redis pub/sub over socket.io rooms. With Socket.io rooms when you publish to listeners, the (browser)clients recieve the message, with redis it is actually the (redis~on server)clients who recieve messages. For this reason, if you want to inform all (server)clients of information specific to each client and maybe do some processing before passing on to browser clients, you are better off using redis. Using redis you can just fire off an event to generate each users individual data, where as with socket.io you have to actually generate all the users unique data at once, then loop through them and send them their individual data, which almost defeats the purpose of rooms, at least for me.
Unfortunately for my purposes I am stuck with redis for now.
Update 2: Ended up developing a plugin to use only 2 redis connections but still allow for individual client processing, see answer below....
Redis pub/sub is great in case all clients have direct access to redis. If you have multiple node servers, one can push a message to the others.
But if you also have clients in the browser, you need something else to push data from a server to a client, and in this case, socket.io is great.
Now, if you use socket.io with the Redis store, socket.io will use Redis pub/sub under the hood to propagate messages between servers, and servers will propagate messages to clients.
So using socket.io rooms with socket.io configured with the Redis store is probably the simplest for you.
I ended up writing a node plugin to allow for many pub-sub clients but only require 2 redis connections instead of a new one on every single socketio connection, it should work in general, figured someone else may find use for it.
This code assumed you have socket.io running and setup, basically in this example any number of socket.io clients can connect and it will always still only use 2 redis connections, but all clients can subscribe to their own channels. In this example, all clients get a message 'sweet message!' after 10 seconds.
Example with socket.io (utilizing redis pub-sub):
var
RPubSubFactory = require('rpss.js');
var
redOne = redis.createClient(port, host),
redTwo = redis.createClient(port, host);
var pSCFactory = new RPubSubFactory(redOne);
io.sockets.on('connection', function(socket){
var cps = pSCFactory.createClient();
cps.onMessage(function(channel, message){
socket.emit('message', message);
});
io.sockets.on('disconnect', function(socket){
// Dont actually need to unsub, because end() will cleanup all subs,
// but if you need to sometime during the connection lifetime, you can.
cps.unsubscribe('cool_channel');
cps.end();
});
cps.subscribe('cool_channel')
});
setTimeout(function(){
redTwo.publish('cool_channel', 'sweet message!');
},10000);
Actual plugin code:
var RPubSubFactory = function(){
var
len,indx,tarr;
var
dbcom = false,
rPubSubIdCounter = 1,
clientLookup = {},
globalSubscriptions = {};
// public
this.createClient = function()
{
return new RPubSupClient();
}
// private
var constructor = function(tdbcom)
{
dbcom = tdbcom;
dbcom.on("message", incommingMessage);
}
var incommingMessage = function(rawchannel, strMessage)
{
len = globalSubscriptions[rawchannel].length;
for(var i=0;i<len;i++){
//console.log(globalSubscriptions[rawchannel][i]+' incomming on channel '+rawchannel);
clientLookup[globalSubscriptions[rawchannel][i]]._incommingMessage(rawchannel, strMessage);
}
}
// class
var RPubSupClient = function()
{
var
id = -1,
localSubscriptions = [];
this.id = -1;
this._incommingMessage = function(){};
this.subscribe = function(channel)
{
//console.log('client '+id+' subscribing to '+channel);
if(!(channel in globalSubscriptions)){
globalSubscriptions[channel] = [id];
dbcom.subscribe(channel);
}
else if(globalSubscriptions[channel].indexOf(id) == -1){
globalSubscriptions[channel].push(id);
}
if(localSubscriptions.indexOf(channel) == -1){
localSubscriptions.push(channel);
}
}
this.unsubscribe = function(channel)
{
//console.log('client '+id+' unsubscribing to '+channel);
if(channel in globalSubscriptions)
{
indx = globalSubscriptions[channel].indexOf(id);
if(indx != -1){
globalSubscriptions[channel].splice(indx, 1);
if(globalSubscriptions[channel].length == 0){
delete globalSubscriptions[channel];
dbcom.unsubscribe(channel);
}
}
}
indx = localSubscriptions.indexOf(channel);
if(indx != -1){
localSubscriptions.splice(indx, 1);
}
}
this.onMessage = function(msgFn)
{
this._incommingMessage = msgFn;
}
this.end = function()
{
//console.log('end client id = '+id+' closing subscriptions='+localSubscriptions.join(','));
tarr = localSubscriptions.slice(0);
len = tarr.length;
for(var i=0;i<len;i++){
this.unsubscribe(tarr[i]);
}
localSubscriptions = [];
delete clientLookup[id];
}
var constructor = function(){
this.id = id = rPubSubIdCounter++;
clientLookup[id] = this;
//console.log('new client id = '+id);
}
constructor.apply(this, arguments);
}
constructor.apply(this, arguments);
};
module.exports = RPubSubFactory;
I mucked around and tried to improve the efficiency as much as I could, but after doing some different speed tests, I concluded this was the fastest I could get it.
For up-to-date version: https://github.com/Jezternz/node-redis-pubsub

node.js simple tcp chat server

I am trying to build a simple tcp chat server, WITHOUT socket.io.
Now, I have no problem broadcasting data across all sockets connected to the server.
My problem is assigning a socket identifier to each connection and retrieving them from an object.
Here is the code:
var net = require('net');
//keep track of sockets
var allSockets = {
sockets: {},
addSocket: function(socket, nick, table) {
this.sockets[table+nick] = socket;
},
removeSocket: function(nick, table) {
if (this.sockets[table+nick] !== undefined) {
this.sockets[table+nick] = null;
delete this.sockets[table+nick];
}
}
};
// create the server
var server = net.createServer(function (socket) {
var connected = false;
var jsoncommand = true;
//first data sent MUST BE json formatted string in this format
//{"nick":"someid","table":"tablenumber"}
var thisnick = "";
var thistable = "";
// get client ip
socket.name = socket.remoteAddress;
//write something on each connect
socket.write("You are connecting from " + socket.name + "\n");
socket.write(socket.name + " joined chat\n");
//handle data streams
socket.on('data', function (data) {
if (jsoncommand) {
//JSON.parse the first data stream
var some = JSON.parse(data);
//assign a socket.id based on nick and table
allSockets.addSocket(socket, some.table, some.nick);
socket.write(some.nick + " joined " + some.table + "\n");
thisnick = some.nick;
thistable = some.table;
connected = true;
//no longer waiting for first stream as JSON
jsoncommand = false;
} else if (connected) {
//write whatever data it recieves (function is below)
broadcast(data, thistable);
} else {
socket.write("You are not connected to any table");
socket.destroy();
connected = false;
jsoncommand = true;
thisnick = "";
thistable = "";
}
});
// remove the socket from allSockets but broadcast
//only to other users on the same table
socket.on('end', function () {
allSockets.removeSocket(thisnick, thistable);
broadcast(thisnick + " has left table " + thistable, thistable);
});
//this function should select from the allSockets object,
//only those, whose property matches "table"
//and write to those sockets only, when called
function broadcast(message, table) {
allSockets.sockets.forEach(function(socket) {
if (socket.hasOwnProperty(table)) {
socket.write(message);
}
});
}
});
server.listen(8000);
console.log("running at port 8000\n");
Just deploy this on your machine and connect with nc to port 8000
and be sure that the first thing you send it is something like
{"nick":"mynick","table":"mytable"}
you will see a message that your nick joined your table.
now if you send it something else, based on the fact that it stored your table name,
it should echo whatever you send it, to you and to other connections with different
nicks but on the same table, but the server dies, throwing an error that the allSockets
object, does not have a "for" or "forEach" method or indexOf, or any other.
SO, how do I correct this?
If my nick is "john" and I joined "my_table", and also "mary", "lisa" and "ana" joine the same "my_table", assuming I don't know their nicks, but I do know they are on "my_table",
HOW do I select from the allSockets object, those sockets that contain "my_table".
I tried hasOwnProperty, but that returns boolean, which only tells me that there are sockets with that property, but how do I put them in a for, or foreach loop in order to write to them.
I know it may be a silly question, or maybe im not even aproaching this correctly, but im a node.js beginner, so any advice is greatly apreaciated.
By the way I put this together from examples across the web.
As for the JSON string, its the first thing being sentby a desktop app on connection. Anyways I chose it for testing purposes so don't bother with it.
I suppose error about forEach happens here:
allSockets.sockets.forEach(function(socket) {
While allSockets.sockets is not Array, but it is object (key > value model, like hash table).
So to loop through each socket in it, you should change loop to:
for(var key in allSockets.sockets) {
var socket = allSockets.sockets[key];
// your logic here
}

Resources