Create a list of Connected Clients using socket.io - node.js

Here are 2 related questions. Posting them together makes more sense.
Question 1
I have a node.js app which emits an event out to all clients, and all current clients will respond with a ready emit. How can I create a list of all the clients that replied to the initial emit, and what kind of identification can be used to distinguish the clients?
Question2:
What I am trying to do after collect a list of connected clients, is to then access a MySQL database table of N number of rows and assign each client X rows each. These rows will be emitted back to their respective clients. How can this be done?
Current Code for Qn 1
Node Code
setInterval(function() {
util.log('Checking for new jobs...');
dbCheckQueue(function(results) { // checks if there are new rows to "distribute" to clients
if (results.length) {
util.log(results.length + ' new jobs found.');
io.sockets.emit('job_available');
}
});
}, 10*1000);
Client-side JS Code
socket.on('job_available', function() {
console.log('Job Available.. Responding with Ready!');
socket.emit('ready');
});
io.sockets.on('connection', function(socket) {
socket.on('ready', function() {
// UPDATE N rows with client_id in column checkout.
// Then SELECTS * from table where checkout = client_id
getListings(client_id, function(listings) {
socket.emit('job', listings); // send jobs
});
});
});
Current Code for Qn 2
The code works for a single client, but how do I loop through all connected clients and perform the same updating of column and selecting of rows?
io.sockets.on('connection', function(socket) {
socket.on('ready', function() {
// UPDATE N rows with client_id in column checkout.
// Then SELECTS * from table where checkout = client_id
getListings(client_id, function(listings) {
socket.emit('job', listings); // send jobs
});
});
});

Socket.io provides you with a public api for that, so instead of hacking something up like Bryan suggest you can use:
io.sockets.clients()
That will returns an array of all connected clients.
If you want all clients connected to a certain namespace:
io.of('/namespace').clients()
But you can even filter it even more.. if you want to have all sockets in a room:
io.sockets.clients('room name here as first argument')
Will return a array of connected sockets for the room room name here as first argument

You will need to keep track of the connected clients yourself. The simple way to do that would be to use an array:
var clients = [];
io.sockets.on('connect', function(client) {
clients.push(client);
client.on('disconnect', function() {
clients.splice(clients.indexOf(client), 1);
});
});
Then you can references that clients array on the server wherever you need to, in your ready event handler or whatever. Something like:
io.sockets.on('connection', function(socket) {
socket.on('ready', function() {
// UPDATE N rows with client_id in column checkout.
// Then SELECTS * from table where checkout = client_id
clients.forEach(function(client, index) {
var client_id = index; // Just use the index in the clients array for now
getListings(client_id, function(listings) {
socket.emit('job', listings); // send jobs
});
});
});
});

Related

Socket.io: How to correctly join and leave rooms

I'm trying to learn Socket.io by building a set of dynamically created chatrooms that emit 'connected' and 'disconnected' messages when users enter and leave. After looking at a couple of questions I've put together something functional but most of the response linked are from people who admit they've hacked together answers and I've noticed there's a more general - and recent - discussion about the right way to do this on the Socket.io repo (notably here and here)
As I'm such a novice I don't know if the work below is an acceptable way to do things or it just happens to incidentally function but will cause performance issues or result in too many listeners. If there's an ideal - and official - way to join and leave rooms that feels less clunky than this I'd love to learn about it.
Client
var roomId = ChatRoomData._id // comes from a factory
function init() {
// Make sure the Socket is connected
if (!Socket.socket) {
Socket.connect();
}
// Sends roomId to server
Socket.on('connect', function() {
Socket.emit('room', roomId);
});
// Remove the event listener when the controller instance is destroyed
$scope.$on('$destroy', function () {
Socket.removeListener('connect');
});
}
init();
Server
io.sockets.once('connection', function(socket){
socket.on('room', function(room){ // take room variable from client side
socket.join(room) // and join it
io.sockets.in(room).emit('message', { // Emits a status message to the connect room when a socket client is connected
type: 'status',
text: 'Is now connected',
created: Date.now(),
username: socket.request.user.username
});
socket.on('disconnect', function () { // Emits a status message to the connected room when a socket client is disconnected
io.sockets.in(room).emit({
type: 'status',
text: 'disconnected',
created: Date.now(),
username: socket.request.user.username
});
})
});
Socket.IO : recently released v2.0.3
Regarding joining / leaving rooms [read the docs.]
To join a room is as simple as socket.join('roomName')
//:JOIN:Client Supplied Room
socket.on('subscribe',function(room){
try{
console.log('[socket]','join room :',room)
socket.join(room);
socket.to(room).emit('user joined', socket.id);
}catch(e){
console.log('[error]','join room :',e);
socket.emit('error','couldnt perform requested action');
}
})
and to leave a room, simple as socket.leave('roomName'); :
//:LEAVE:Client Supplied Room
socket.on('unsubscribe',function(room){
try{
console.log('[socket]','leave room :', room);
socket.leave(room);
socket.to(room).emit('user left', socket.id);
}catch(e){
console.log('[error]','leave room :', e);
socket.emit('error','couldnt perform requested action');
}
})
Informing the room that a room user is disconnecting
Not able to get the list of rooms the client is currently in on disconnect event
Has been fixed (Add a 'disconnecting' event to access to socket.rooms upon disconnection)
socket.on('disconnect', function(){(
/*
socket.rooms is empty here
leaveAll() has already been called
*/
});
socket.on('disconnecting', function(){
// socket.rooms should isn't empty here
var rooms = socket.rooms.slice();
/*
here you can iterate over the rooms and emit to each
of those rooms where the disconnecting user was.
*/
});
Now to send to a specific room :
// sending to all clients in 'roomName' room except sender
socket.to('roomName').emit('event', 'content');
Socket.IO Emit Cheatsheet
This is how I inform users of a "disconnecting user"
socket.on('disconnecting', function(){
console.log("disconnecting.. ", socket.id)
notifyFriendOfDisconnect(socket)
});
function notifyFriendOfDisconnect(socket){
var rooms = Object.keys(socket.rooms);
rooms.forEach(function(room){
socket.to(room).emit('connection left', socket.id + ' has left');
});
}
Here is a working method.
I am using socketio "socket.io": "^2.3.0" on server side. On Client side is android
// SocketIO
implementation('io.socket:socket.io-client:1.0.0') {
// excluding org.json which is provided by Android
exclude group: 'org.json', module: 'json'
}
Following is the code that is working for me.
// Join Chat Room
socket.on('ic_join', function(data) {
// Json Parse String To Access Child Elements
let messageJson = JSON.parse(data)
let room1 = messageJson.room1
console.log('======Joined Room========== ')
console.log(room1)
socket.join(room1, function(err) {
console.log(io.sockets.adapter.rooms[room1].length);
console.log(err)
})
})
// Leave Chat Room
socket.on('ic_leave', function(data) {
// Json Parse String To Access Child Elements
let messageJson = JSON.parse(data)
let room1 = messageJson.room1
console.log('======Left Room========== ')
console.log(room1)
socket.leave(room1, function(err) {
if (typeof io.sockets.adapter.rooms[room1] !== 'undefined' && io.sockets.adapter.rooms[room1] != null) {
console.log(io.sockets.adapter.rooms[room1].length);
console.log(err)
} else{
console.log("room is deleted")
}
})
})
For anyone reading this beyond 2/1/2021, using socket.io 3.1.0 and hopefully later, you can reference my example. I have found that the example on how to do this in socket.io's documentation is incorrect. They claim that in the disconnect event that socket.rooms is an object. While is uses the block container and is comma separated, there are no key pairs, meaning their demonstration of const rooms = Object.keys(socket.rooms) returns an empty value. It's creating an array out of an object that is really just an array. To my knowledge {} can only be used for block statements and objects. By some quirk, NodeJS is treating it like a normal array. I assign custom, 4 digit rooms on each connect event. So I have on the disconnect event I have the server skim through all the rooms, and if it encounters a room name with a length of 4, it tells everyone in the room that the size of the room decreased by one. On the client side, I have a socket event listener monitoring for this and when it's detected, updates a innerHTML property so that the clients can display the number of connected users.
//client side code:
socket.on('roomSize', (roomSize) => {
document.getElementById('clientCount').innerHTML = roomSize + ' clients connected';
});
//Server side code:
io.on("connection", (socket) => {
socket.on('createRoom', () => {
let ID = makeID(4)
while (io.sockets.adapter.rooms.has(ID)) {
ID = makeID(4)
}
socket.join(ID);
socket.emit('setID', ID);
});
socket.on("joinRoom", (room) => {
socket.join(room);
let roomSize = io.sockets.adapter.rooms.get(room).size
io.in(room).emit('roomSize', roomSize);
socket.emit('roomJoined', room, roomSize);
});
socket.on('disconnecting', function() {
let rooms = socket.rooms;
rooms.forEach(function(room) {
if (room.length === 4) {
let roomSize = io.sockets.adapter.rooms.get(room).size - 1
io.in(room).emit('roomSize', roomSize);
}
});
});
function makeID(length) {
var result = '';
var characters = '0123456789';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
})

nodejs + mysql + individual user

I am trying to build a client which will send the id ( ex. email ) to nodejs program on server. Email will actually be of the logged-in user.
And nodejs program should keep updating a particular div on client side(say pushing data every 3 seconds) with relevant data for that email address.
Same should be done on multiple machines, wherein every machine has separate person logged-in via email. Something like what happens on gmail.
I have built it as follows, but when i open it from multiple clients, the value of latest client(email) overwrites the value of previous ones and all client show data for latest email only.
Code is as shown below for both server and client.
server.js
var app = require('http').createServer(handler),
io = require('socket.io').listen(app),
fs = require('fs'),
mysql = require('mysql'),
connectionsArray = [],
profile_id,
last_count = 0, //this variable is to check previous count value
connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '', //put your own mysql pwd
database: 'test', //put your database name
port: 3306
}),
POLLING_INTERVAL = 1000,
pollingTimer;
// If there is an error connecting to the database
connection.connect(function(err) {
// connected! (unless `err` is set)
console.log(err);
});
console.log('DB connected');
// creating the server ( localhost:8000 )
app.listen(8000);
// on server started we can load our client.html page
function handler(req, res) {
fs.readFile(__dirname + '/indexnew.html', function(err, data) {
if (err) {
console.log(err);
res.writeHead(500);
return res.end('Error loading client.html');
}
console.log('File loaded');
res.writeHead(200);
res.end(data);
});
}
/*
* HERE IT IS THE COOL PART
* This function loops on itself since there are sockets connected
* to the page. Upon Update it only emits the notification if
* the value has changed.
* Polling the database after a constant interval
*/
var pollingLoop = function() {
sql = "SELECT count(*) as c FROM activity_log WHERE notified=0 and (profile_id = '" + profile_id + "')";
console.log(sql);
// Doing the database query
var query = connection.query(sql),
users = []; // this array will contain the result of our db query
// setting the query listeners
query
.on('error', function(err) {
// Handle error, and 'end' event will be emitted after this as well
console.log(err);
updateSockets(err);
})
.on('result', function(count) {
// it fills our array looping on each user row inside the db
users.push(count);
// loop on itself only if there are sockets still connected
if (connectionsArray.length) {
pollingTimer = setTimeout(pollingLoop, POLLING_INTERVAL);
updateSockets({
users: users,
count: count.c
});
}
})
};
// creating a new websocket to keep the content updated without any AJAX request
io.sockets.on('connection', function(socket) {
console.log('Connected');
//This variable is passed via the client at the time of socket //connection, see "io.connect(..." line in client.html
profile_id = socket.handshake.query.profile_id;
console.log('Number of connections:' + connectionsArray.length);
// starting the loop only if at least there is one user connected
if (!connectionsArray.length) {
pollingLoop();
}
socket.on('disconnect', function() {
var socketIndex = connectionsArray.indexOf(socket);
console.log('socket = ' + socketIndex + ' disconnected');
if (socketIndex >= 0) {
connectionsArray.splice(socketIndex, 1);
}
});
console.log('A new socket is connected!');
connectionsArray.push(socket);
});
var updateSockets = function(data) {
if (last_count != data.count) {
// adding the time of the last update
data.time = new Date();
// sending new data to all the sockets connected
connectionsArray.forEach(function(tmpSocket) {
tmpSocket.volatile.emit('notification', data);
});
}
last_count = data.count;
};
indexnew.html
<html>
<head>
<title>Push notification long polling server streaming on a MySQL db</title>
<style>
dd,
dt {
float: left;
margin: 0;
padding: 5px;
clear: both;
display: block;
width: 100%;
}
dt {
background: #ddd;
}
time {
color: gray;
}
</style>
</head>
<body>
<time></time>
<div id="container">Loading ...</div>
<script src="socket.io.js"></script>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
<script>
var rno = Math.floor((Math.random() * 10) + 1);
// create a new websocket
var socket = io.connect('http://localhost:8000/?profile_id=' + rno);
// on message received we print all the data inside the #container div
socket.on('notification', function(data) {
var usersList = "<dl>";
$.each(data.users, function(index, count) {
usersList += "<dt>" + count.c + "</dt>\n";
});
usersList += "</dl>";
$('#container').html(usersList);
$('time').html('Last Update:' + data.time);
$('sql').html('Last Update:' + data.sql);
});
</script>
</body>
</html>
The way your pollingLoop function and query is structured you will only ever retrieve the last profile_id. Here is why:
The profile_id variable is a global variable, existing on the server thread. The way you've programmed it so far, it seems that you want your pollingLoop to handle all of your sockets at once, but then inside the pollingLoop, you use the global variable profile_id to pull down the information from the database. Every time a new user connects the 'connected' event fires, which updates the profile_id to that user.
Here are some options for what you can do to solve for this:
Construct your query to pull all the profile_id's that are connected,
and build your update_sockets to only send the count for each
profile_id to the appropriate socket.
You can use "group by" to help
with your counts. You could make the pollingLoop run for each
connected user individually. You could keep your query as it is.
Either way you will have to fix your profile_id, because it doesn't make sense to have it contain a single user's unique id in a global setting. If you truly want the profile_id to be a session-based unique ID, doing a random number is a bit dangerous. I would suggest using the socket.id that socket.io exposes for each socket. This way you don't have to worry about it being unique, you don't have to pass it along, and you don't have to store it yourself. The socket.id will be available to you in the connectionsArray since that contains all your sockets. So in the pollingLoop you can loop over the ID's to construct your query, and in the updateSockets you can pair the results back to the socket that needs to receive the data particular to that user.
One more note about a comment in your connection event:
// starting the loop only if at least there is one user connected
This comment is inaccurate, I think it should say:
// starting the loop only if there are no other users connected yet
I hope that helps you!

Socket.IO - setInverval every two minutes (Node JS)

I have an event in socket.io which broadcasts how many users are online (based on who is logged in) to the user.
What I think should be happening is that I ask the server to query the database once every two minutes using setInterval, and a rowset is returned, then emitted to the client.
What is happening though, is that for each user connected to the socket, it is calling the database on the server and pushing to the client.
I'm uncertain why this is doing this - I'd understand if I was asking it to do this from the client, but as it's the server emitting the event to the client, why would it be doing this several time for each user connected?
Thanks
io.on("connection", function(socket) {
// Update online users' every two minutes
setInterval(function() {
var roomNum = 0;
var outObj = {};
model.get_online_users(function(err, rowset) {
// Loop thorugh the rowset, set the roomNum and built up out_obj to output
io.to("room_number:"+roomNum).emit("online-users", outObj);
}); // End `model callback`
}, 120000); // End `get_online_users`
}); // End `io.on("connection")`
The code in io.on("connection", function(socket) { something(); }); is called for EVERY connected user, if you put a loop in this, the loop will loop paralelly for every connected user.
The setInterval should be outside of your io.on("connection", function(socket) { });, and it will run once, from your node server starting to the shutdown of the server.
Example :
setInterval(function() {
var roomNum = 0;
var outObj = {};
model.get_online_users(function(err, rowset) {
io.to("room_number:"+roomNum).emit("online-users", outObj);
});
}, 120000);

Setting up a socket.io room for a specific group based on url. Node.js, Express, Angluarjs

I have a web application running on node using express, angular, mongodb and socket.io where users belong to groups. In those groups I need to set up a chat socket whereby only the people who belong to that group can see the messages specific to that group and only send messages to other users in that group. At the moment I have managed to set up the socket.io chat room based on the guidelines on their site, but the messages sent are visible to all other groups. I have read that I need to set up certain rooms, but I am not sure how to set it up so that the room is specific to the group.
The group is held at a domain: for example... www.mydomain.com/groups/groupId/room
The groupId is a unique Id which is generated by MongoDB. I am using Angularjs on the client side, and have a group controller for the groups. If anyone has any ideas how to achieve this or could point me in the right direction I'd be extremely grateful.
My current server side code:
var io = require('socket.io').listen(server);
io.sockets.on('connection', function(socket) {
socket.on('connectToServer', function (data) {
var name = data.name;
io.sockets.emit('user-join', name + " has connected to the group");
});
socket.on('send msg', function (data) {
io.sockets.emit('get msg', data);
});
});
and my client side code in the groupController, in which I access a Global service which stores the current user's details is as follows:
var socket = io.connect();
$scope.msgs = [];
$scope.sendMsg = function () {
socket.emit('send msg', $scope.msg.text);
$scope.msg.text = "";
}
socket.on('get msg', function (data){
$scope.msgs.push(data);
});
var currentUser = Global.currentUser();
var name = currentUser.username;
socket.emit('connectToServer', {name: name});
socket.on('user-join', function (data) {
$scope.msgs.push(data);
});
I'd recommend setting up namespaces for each room you have your chat in. I did something similar in my own code. Note: Rooms and namespaces are a little different from each other in socket.io itself (socket.io has both: http://socket.io/docs/rooms-and-namespaces/).
In the Server code:
I have a method under socket.on('connection') that is similar to
socket.on('groupConnect', function(group){
var groupNsp = io.of('/' + group);
}
This essentially makes sure that a namespace is exists under the name of the desired one. It doesn't mess it up or reset the namespace when it is called again.
Then for receiving the messages:
socket.on('message', function(data){
var msg = data.msg;
var nsp = data.nsp;
io.of(nsp).emit('message', msg);
}
You could also add the nsp to the data you have already and then just send the data again to the clients.
Then, in the client code:
var socketOut = io.connect('http://yourdomain:1337/');
var someGroupOrMethodToGetGroup;
socketOut.emit('groupConnect', someGroupOrMethodToGetGroup);
var nsp;
setTimeout(function(){
socket = io.connect('http://yourdomain:1377/' + someGroupOrMethodToGetGroup);
socket.on('message', function(msg){
displayMessage(msg);
}
nsp = '/' + someGroupOrMethodToGetGroup;
}, 1500);
Then in my displayMessage code I have:
socketOut.emit('message', { msg: desiredMessage, nsp: nsp });

using socket io to get updates

i'm using in socket.io to get live updates from database.
here is my code:
var q = "SELECT * FROM notifications";
db.query(q, function(err, rows, fields) {
if(rows[0]) {
io.sockets.emit('newNotifications', rows[0]);
}
});
its work, but i want to add WHERE userid = "+myuserid+"
the question is how to do this?
i tried to do this with this code:
socket.on('connect', function () {
socket.send({"myuserid":"1"});
console.log('connected');
});
thanks.
You basically need to tie a userid to a particular a particular socket. How you do this is dependent on how you're handling authentication.
Once you have a userid attached to the socket, you have a couple options for getting them individual notifications. The most straightforward is to do as you wanted above. Create a query with a condition limiting the results. You need to do this on a per-socket basis though, so you'll need something like the following.
data.watchForNotification = function(userId, fn) {
// Please sanitize this and make sure you're not vulnerable to sql injection attacks
var q = "SELECT ... WHERE UserId = " + userId;
data.watchedUsers[userId] = setInterval(function() {
db.query(q, function(err, rows, fields) {
if(rows.[0]) {
fn(rows[0]);
}
});
}, 10000);
}
data.stopWatching(userId) {
if(data.watchedUsers[userId]) {
clearInterval(data.watchedUsers[userId]);
delete data.watchedUsers[userId];
}
}
io.on('connection', function(socket) {
// authenticate and set socket.userId
data.watchForNotifications(socket.userId, function(notification) {
socket.emit('notification', notification);
});
socket.on('disconnect', function() {
data.stopWatching(socket.userId);
});
});
This will setup an interval that checks every 10 seconds for new notifications and will send those to the connected socket when they come in. It will also stop the database queries for that user if they disconnect. There is a lot of pseudo code and abstraction work going on here, but it should be enough to get you where you want to go.

Resources