Best Approach to send real time analytics using socket.io - node.js

I am using socket.io and node.js/mongoDB for an app which will send real time analytics between Parents and Drivers
Let's say Driver is moving along a path and for every location change he will send his location to a list of specific parents.
I can think of one approach to achieve such functionality
1- I create two arrays
var userSockets = {};
var driverSockets = {};
Whenever a user/driver is connected i do
For Driver - driverSockets[accId] = socket
For User - userSockets[accId] = socket
Now if a driver has to emit a location change, he will do something like
userSockets[userId].emit(abc)
I would like to know if this approach is better? Would it be better to save users as onlineUsers in MongoDB but even then how to access their sockets to emit data.
What would be the best approach.

You should use "room" to store online user and emit to this room(channel) when location change.
//join a room(channel)
socket.join('online');
//sending to sender client, only if they are in 'online' room(channel)
socket.to('online').emit('location', {user_type:'driver'});

Here is the example code to seperate driver and parents in different array with user_type key-word and send driver location to all the parents in real time. Furthermore, We can add users mobile number to send specific driver location to a specific parent etc.
var parents = [];
var drivers = [];
var users = [];
io.on('connection', function(socket){
users.push(socket);
socket.on('disconnect', function(msg){
var i = users.indexOf(socket);
users.splice(i, 1);
var j = parents.indexOf(socket);
if(j !== -1){
parents.splice(j, 1);
}
var k = drivers.indexOf(socket);
if(k !== -1){
drivers.splice(k, 1);
}
});
socket.on('register', function(msg){
console.log(msg);//send json- {"user_type":"driver"}
var data = JSON.parse(msg);
var i = users.indexOf(socket);
if(data.user_type === 'driver'){
drivers.push(users[i]);
}else{
parents.push(users[i]);
}
users[i].emit('register', '{"status":"registered"}');
});
socket.on('location', function(msg){
console.log(msg);//send json- {"user_type":"driver","location":"lat,long"}
var data = JSON.parse(msg);
if(data.user_type === 'driver'){
for(var x=0;x<parents.length;x++){
parents[x].emit('location', '{"user_type":"driver", "location":"lat,long"}');
}
}
});
});

Related

NodeJs Socket programming how to handle and manage Sockets? (Without Using socket.io) What is the efficient way?

After connection to the Server, every time Data coming from this connection (Socket.on('data',...)), Server fetches UserID from Data and check the ClientList (array of Socket objects), to see if Socket with this UserID exists in ClientList, if not : adds UserID as a property of Socket and then adds Socket object to Client list.
So when user with ID=1 want to send a message to user with ID = 2,
Server search for Socket with UserID = 2 in ClientList to find the right Socket and send user 1's message to the found Socket (user 2's socket).
I'm trying to accomplish this without using socket.io! That's what my employer made me to do! :))
Now my question is: am I doing this right? Is this efficient to check ClientList array (every time a connection send Data) to see if this UserID exists in ClientList? if not, then what is the right and efficient way? there is no problem with my code and it works. but what if there are thousands of connections?
Any Sample code , example or link would be appreciated. Thank you.
here is a pseudo code :
var net = require('net');
var Server = net.createServer();
var myAuth = require('./myAuth');
var ClientList = [];
Server.on('connection', function(Socket){
Socket.UserData = {}; // I want to add user data as a property to Socket
Socket.on('data', function (Data) {
var userID = myAuth.Authenticate_and_getUserID(Data);
if(userID != undefined){
var found = false;
ClientList.filter(function(item){
// check if Socket is in ClientList
if(item.UserData.ID == userID){
// client was connected before
found = true;
}
});
if(!found){
// this is a new connection, Add it to ClientList
Socket.UserData.ID = userID;
ClientList.push(Socket);
}
}
Socket.once('close', function(has_error){
var index = ClientList.indexOf(Socket);
if (index != -1){
ClientList.splice(index, 1);
console.log('Client closed (port=' + Socket.remotePort + ').');
}
});
});
UPDATE for clarification:
is this efficient to look into ClientList every time Data is coming to Server, to check for receiverID (presence of receiver) and to Update ClientList with current connection UserID if not exists?
how should I manage new connections(users) and store them in server for later use when number of users are thousands or millions! NOT 10 or 100. How socket.io is doing this?
later usages could be:
check to see if one specific user is online (have an object in ClientList)
send realtime message to a user if he/she is online
etc . . .
Actually I am doing this wrong!
Arrays in JavaScript are passed by reference! So there is no need to update ClientList every time a Socket send data.
Therefor the Code changes like Following:
var net = require('net');
var Server = net.createServer();
var myAuth = require('./myAuth');
var ClientList = [];
Server.on('connection', function(Socket){
ClientList.push(Socket);
Socket._isAuthorized = false;
// when socket send data for the first time
// it gets authenticated and next time it send data
// server does not authenticate it
Socket.on('data', function (Data) {
var userID = getUserID(Data);
if(Socket._isAuthorized != true){
if(authenticate(Socket)){
Socket._isAuthorized = true;
Socket._userID = userID;
return;
}
}
// do something with data...
}
Socket.once('close', function(has_error){
var index = ClientList.indexOf(Socket);
if (index != -1){
ClientList.splice(index, 1);
console.log('Client closed (port=' + Socket.remotePort + ').');
}
});
});
And its efficient!

Socket IO: Number of users in a room

Using Socket.IO v.1.3.5 for node.JS...
How can I get the number of real users (not sessions) that are connected in a room?
Number of sessions:
var room = io.sockets.adapter.rooms[roomId] || {};
var numSocketsInRoom = Object.keys(room).length;
But if the same user is connected in multiple browser tabs, the numSocketInRoom is increased because is the number of sessions, and not the real number of users.
In my case, any socket have the user saved in socket.userId, but I don't know how indicate this to get the number of users connected in a room...
Thank you!
My solution finally was this code, this code is executed after join (isConnect = true) or leave (isConnect = false) the room:
//Get the room:
var room = io.sockets.adapter.rooms[roomId] || {};
//Declaration of number of users variable
var numUsers;
//If there are any user connected in the room, create the object
if(room.user===undefined)
room.user = {};
//If you join the room and have another opened session, increase the number of sessions
if(room.user[socket.request.user.id] && isConnect)
room.user[socket.request.user.id] += 1;
//If you leave the room and have another opened session, decrease the number of sessions
else if(room.user[socket.request.user.id] && !isConnect){
room.user[socket.request.user.id] -= 1;
//If the number of sessions is 0, delete the user from the object
if(room.user[socket.request.user.id]===0)
delete room.user[socket.request.user.id];
}
//If the user don't have any session and join the room, put 1 session in the object
else if(isConnect){
room.user[socket.request.user.id] = 1;
}
//Get the number of users from room.user object
numUsers = Object.keys(room.user).length;

Broadcasting with shoe sockjs

I am trying to setup a simple scenerio using shoe + dnode +sockjs and I do not know how to broadcast a message to all users connected to the web application.
Do you know if there is a function or method which manage this? or should it be make by "hand"?
AFAIK, you have to roll it by "hand" as you say. Here is what I do:
server.js:
var shoe = require('shoe')
var connectedClients = {}
var conCount = 0
var sock = shoe(function(clientStream) {
clienStream.id = conCount
connectedClients[clientStream.id] = clientStream
conCount += 1
})
somewhere else in your server-side program:
//write to all connected clients
Object.keys(connectedClients).forEach(function(cid) {
var clientStream = connectedClients[cid]
clientStream.write(yourData)
})
Note, you'll want to introduce additional logic to only write to connected clients, so you'll want to remove disconnected clients from connectedClients, something like delete connectedClients[id].
Hopefully that helps.

Handle XMPP presence with Node

I'm using the node-xmpp module to connect to a XMPP server and join a group chat. Connecting to the server, setting the presence, joining the room and reading out messages works so far. But I want to receive the userlist of the room too.
The XMPP protocol requires to send a presence stanza when the client enters the room (http://xmpp.org/extensions/xep-0045.html#enter-pres). But how can I now parse it in node?
My code currently looks like this:
var xmpp = require('node-xmpp');
// Create the XMPP Client
var cl = new xmpp.Client({
jid: jid,
password: password,
reconnect: true
});
// Do things when online
cl.on('online', function() {
util.log("We're online!");
// Set client's presence
cl.send(new xmpp.Element('presence', { type: 'available' }).c('show').t('chat'));
cl.send(new xmpp.Element('presence', { to: room_jid+'/'+room_nick }).c('x', { xmlns: 'http://jabber.org/protocol/muc' }).c('history', {seconds: 1}));
// Send keepalive
setInterval(function() {
cl.send(' ');
}, 30000);
cl.on('stanza', function(stanza) {
// always log error stanzas
if (stanza.attrs.type == 'error') {
util.log('[error] ' + stanza);
return;
}
// ignore everything that isn't a room message
if (!stanza.is('message') || !stanza.attrs.type == 'chat') {
return;
}
var body = stanza.getChild('body');
// message without body is probably a topic change
if (!body) {
return;
}
// Extract username
var from, room, _ref;
_ref = stanza.attrs.from.split('/'), room = _ref[0], from = _ref[1];
var message = body.getText();
// Log topics and messages to the console
if(!from) {
util.log('Topic: ' + message);
} else {
util.log('[' + from + ']: ' + message);
}
});
});
I already tried triggering presence by using
if(stanza.is('presence')) {}
within the cl.on('stanza') part but it doesn't work.
UPDATE: I'm describing a new method now which doesn't require the client to send requests.
Background: When the client joins a group chat, the server returns presence stanzas which contain information about the connected users to the group chat.
cl.on('stanza', function(stanza) {
// always log error stanzas
if (stanza.attrs.type == 'error') {
util.log('[error] ' + stanza);
return;
}
if(stanza.is('presence')){
// We are only interested in stanzas with <x> in the payload or it will throw some errors
if(stanza.getChild('x') !== undefined) {
// Deciding what to do based on the xmlns attribute
var _presXmlns = stanza.getChild('x').attrs.xmlns;
switch(_presXmlns) {
// If someone is joining or leaving
case 'http://jabber.org/protocol/muc#user':
// Get the role of joiner/leaver
_presRole = stanza.getChild('x').getChild('item').attrs.role;
// Get the JID of joiner/leaver
_presJID = stanza.getChild('x').getChild('item').attrs.jid;
// Get the nick of joiner/leaver
_presNick = stanza.attrs.from.split('/')[1];
// If it's not none, this user must be joining or changing his nick
if(_presRole !== 'none') {
// We are now handling the data of joinging / nick changing users. I recommend to use an in-memory store like 'dirty' [https://github.com/felixge/node-dirty] to store information of the users currentliy in the group chat.
} else {
// We are now handling the data of leaving users
}
break;
}
return;
}
return;
}
OLD METHOD
I previously described a method how to query the server for current users in the group chat. By maintaining a store where all user traffic (joining, leaving, nick changing) is stored, this is no longer required. However you could still use it to make sure the data is consistent by issues like a presence stanza was not delivered to the client correctly. That's the reason it's still described below:
To request a list with users connected to the room, you need to perform the following actions:
First send a request to the server and ask for the user list:
cl.send(new xmpp.Element('iq', {from: jid, to: room_jid, type: 'get' }).c('query', { xmlns: 'http://jabber.org/protocol/disco#items' }));
then listen for iq-stanzas, parse them and populate an array with the data:
// Catching the requested user list
if(stanza.is('iq')){
// Fetching usernames from return data (data structure: http://xmpp.org/extensions/xep-0045.html#example-12)
var _items = stanza.getChild('query').getChildren('item');
var users = new Array();
for(var i = 0; i<_items.length; i++) {
// We are building an object here to add more data later
users[i] = new Object();
users[i]['name'] = _items[i].attrs.name;
}
console.log(util.inspect(users, {depth: null, colors: true}));
return;
}
This will provide you with a user list. To request unique JIDs you have to probe every user. To keep the list up to date, you should remove users when they leave and add + probe when they join.

how do I store socket resources from specific users with socket.io?

I'm designing a chat script which I test on my machine using different browsers. I'm tryng to send messages to specific users with socket.io, so here it is :
client:
socket.on('msgFromServer', function (data) {
message = data['message'],
from = data['from'],
to = data['to'];
if($('#chatbox.'+from).dialog("isOpen") === true){
$('#chatbox.'+from+' #messageOutput textarea.readOnly').text(message);
}
else if(($('#chatbox.'+from).dialog("isOpen") !== true)){
createChatbox(from,to,message);
}
});
server:
var users = {};
io.sockets.on('connection', function (socket) {
if( ( users.hasOwnProperty(req.session.name) === false))
users[req.session.name] = socket;
socket.on('msgToServer', function (data) {
for (var u in users){
console.log("%s | %s",u,users[u]);
}
});
});
Well, I'll talk about the structure of code related to the server. It is in charge of storing a user on a 'connection' event. The problem starts when I reload the page: it stores the user from browser A in the users object, if I reload and reconnect stores it again , but when I ask which are the contents of the users object in browser B ... the info is outdated and does not show the same result as when I ask which are the contents of the object in broser A, even though I'm trying to do some cheking of nullity to store vals if users is empty --> if( ( users.hasOwnProperty(req.session.name) === false)). Basically, what I need is a means of storing each socket resource in a container(in fact, doesn't necessarily needs to be an object) with an identifier(req.session.name) and to have such container available to all sessions in all browsers, so when server receives a message from browser A to browser B it could identify it and emit a response to browser B.
I got an I idea of what I wanted from https://github.com/generalhenry/specificUser/blob/master/app.js and http://chrissilich.com/blog/socket-io-0-7-sending-messages-to-individual-clients/
If you look carefully at the code... in chrissilich.com , the author states that we need to store the 'socket.id' (users[incoming.phonenumber] = socket.id), whereas in git generalhenry states we have to store the 'socket'(users[myName] = socket) resource. The latter is the correct one , because the values of socket.id tend to be the same in both browsers... and that value changes automatically , I don't know why is there... I suppose in earlier versions of node it worked that way.
The problem is that socket.id identifies sockets, not users, so if an user has several tabs opened at same time, every tab would have different socket.id, so if you store only one socket.id for an user, every time you assign it, you overwrite previous socketid.
So, beside other possible problems, at least you need to do this or it won't work. I bet that you say about 1 socket for all browsers is that you overwrite the id every time (it happened to me when I started using Socket.IO)
As a general rule, remember that you manage CONNECTIONS and not USERS... an user can have more than one connection!.
On connection
function onConnection( socket ) {
var arr = users[incoming.phonenumber] || null;
if( !arr )
users[incoming.phonenumber] = arr = [];
if( arr.indexOf( socket.id ) === -1 )
arr.push( socket.id ); // Assigns socket id to user
}
On disconnection
function onDisconnect( socket ) {
var arr = users[incoming.phonenumber] || null;
if( !arr ) return; // Should not happen since an user must connect before being disconnected
var index = arr.indexOf( socket.id );
if( index !== -1 )
arr.splice( index, 1 ); // Removes socket id from user
}

Resources