How does a ws websocket tell if connection event corresponds to an existing client in the clients list? - node.js

I am using the ws module to implement a WebSocket server in NodeJS. On the client-side, I request the connection using
webSocket = new WebSocket(url);
On the server-side, I have code that handles the 'connect' event, in which I print out the number of clients using
console.log("Total number of clients = " + wsServer.clients.size);
If I open the client-side in different tabs (or browsers), the number of clients is incremented for each new connection (as expected).
If I refresh a page, the webSocket = new WebSocket(url); code is called again and on the server the code handling the 'connect' event (see below) is also called again. However, in this case, the number of clients is not incremented. This is nice behaviour as it maintains the number of connections one wants, but I cannot see how this is done. I want to be able to test if this is an existing connection as I have a chat room running that says 'so-and-so' has joined when a new connection is made. However, I don't want this to happen every time a user refreshes their page.
Here is the server-side event-handler:
// On connection
wsServer.on("connection", function (ws, req) {
let { query } = url.parse(req.url, true);
ws.userName = ("name" in query) ? query.name : null;
ws.roomCode = ("roomCode" in query) ? query.roomCode : null;
ws.userPIN = ("PIN" in query) ? query.PIN : null;
console.log(ws.userName + " joined room " + ws.roomCode);
console.log("Total number of clients = " + wsServer.clients.size);
let data = {
userName: "Server",
message: ws.userName + " joined the room."
};
let dataStr = JSON.stringify(data);
// Loop through each client
wsServer.clients.forEach((client) => {
// Check if client is ready and in same room
if ((client.readyState === WebSocket.OPEN) && (client.roomCode == ws.roomCode)) {
client.send(dataStr);
}
});
// On message event
ws.on("message", function (msg) {
// Loop through each client
wsServer.clients.forEach((client) => {
// Check if client is ready and in same room
if ((client.readyState === WebSocket.OPEN) && (client.roomCode == ws.roomCode)) {
client.send(msg.toString());
}
});
});
});
As one can see, I do modify the client by adding name and room code fields to it but these are not present when the 'connect' event fires, implying the object is being created from scratch. However, no extra client is being added to the clients list, so what I would like to understand is:
how does the ws package know this is an existing connection?
how can I test for this?
Any advice would be gratefully received!

On investigation, it turns out the when the user refreshes the page, the WebSocket connection is closed and then re-opened. Hence there is no testing for an existing client, the existing client is just deleted on the server and the new connection added - hence the number of clients does not appear to change.

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 emit to specific user and give them x amount of time to respond

I have an online chat. It uses rooms. If a user sends message and the other user is not online, it should increment the "missed messages" counter. I tried to create a timeout with setTimeout and if they emit an event it clears that timeout.
However chatOnline doesn't fire as I expected to, which leads to it always reporting the user is offline and incrementing the counter for missed_texts column in rethinkdb (which is not shown because it isn't relevant).
How can I retrieve if the user is online from socket.io? My goal is to avoid having to store presence info in the database, which could get out of control quickly.
Code I tried:
socket.on('chatSend',function(data){
if(socket.client.user.room_id !== null){
//were storing some crap in the socket object for easy retrieval.
data.user_id = socket.client.user.id;
data.room_id = socket.client.user.room_id;
data.timestamp = ~~(new Date() / 1000);
//insert message into chat table
r.table('chat').insert(data).run().then(function(res){
//retrive generated record from table
r.table('chat').get(res.generated_keys[0]).run().then(function(data2){
io.sockets.in(data2.room_id).emit('chatNew',data);//emit to all users in room
log('chatSend');
//attempt to see if the other user is online
getOtherUser(data2.room_id,socket.client.user.id,function(tid){
log('other user id: %d',tid);
//all users automatically join a room matching their user id when connecting.
//unsure how to see if the user is online. this doesnt work.
//this is what i need help with. retrieving the other users socket resource if they are online,
//and if they are not then return null or false, etc so i can work with that.
var othersocket = io.sockets.in(tid);
//if timeout completes before they respond, they are not online.
var tmptime = setTimeout(function(){
log('other user not online.');
othersocket.removeListener('chatOnline',tmpfunc);
missedTexts(data2.room_id,tid,'INCR');
},5000);
var tmpfunc = function(){
clearTimeout(tmptime);
//remove the listener
othersocket.removeListener('chatOnline',tmpfunc);
};
//emit chatOnline to other user socket
//when they respond, cleartimeout, resulting in counter not being incremented.
othersocket.on('chatOnline',tmpfunc);
othersocket.emit('chatOnline');
});
});
});
}
});

WebRTC - How to establish a peer connection after offers and answers

I have a node.js running which the users will connect to. The offer and answer will be generated and sent through node.js.
I'm trying to establish a peer connection and send over a camera stream. I tried my code without using ICE candidates as the computers where in the same subnet. I tried to implement ICE afterwards. I'm not sure if i've done it right though or if it's even needed if the computers are on the same subnet.
var localStream;
//Connect to signaling server
var signalingChannel = io.connect('http://85.134.54.193:8001');
console.log("Connect to signaling server");
var servers = null;
var video1;
var video2;
var audio1;
var audio2;
var cfg = {"iceServers":[{"url":"stun:stun.l.google.com:19302"}]};//{ "iceServers": [{ "url": "stun:stun.l.google.com:19302" }] };
var con = { 'optional': [{'DtlsSrtpKeyAgreement': true}, {'RtpDataChannels': true }] };
var peerConnection;
//Runs after the page has been loaded
window.onload=function(){
//Gets ID for the video element which will display the local stream
video1 = document.getElementById("audio1");
//Gets ID for the video element which will display the remote stream
video2 = document.getElementById("audio2");
audio1 = document.getElementById("audio1");
audio2 = document.getElementById("audio2");
}
//Start button function
function caller(){
peerConnection = new webkitRTCPeerConnection(cfg);
navigator.webkitGetUserMedia({'audio':true, video:true}, function (stream) {
console.log("Got local audio", stream);
video1.src = window.webkitURL.createObjectURL(stream)
peerConnection.addStream(stream);
},
function ( err ) {
console.log( 'error: ', err );
});
console.log("Calling");
//Create Offer
peerConnection.createOffer(function (offerDesc) {
console.log("Created local offer", offerDesc.sdp);
peerConnection.setLocalDescription(offerDesc);
}, function () { console.warn("Couldn't create offer"); });
//ICE Candidates Generator
peerConnection.onicecandidate = function(evt) {
//When The Ice Gathering is complete
if (evt.target.iceGatheringState == "complete") {
//Create a new offer with ICE candidates
peerConnection.createOffer(function(offer) {
console.log("Offer with ICE candidates: " + offer.sdp);
signalingChannel.emit('offer', JSON.stringify(offer));
console.log("offer sent");
signalingChannel.on('answer', function(data){
console.log("Receive answer");
//The answer is set as the remote description for the offerer
peerConnection.setRemoteDescription(new RTCSessionDescription(JSON.parse(data)));
console.log("Set remote desc");
peerConnection.onaddstream = gotRemoteStream;
console.log("Add remote stream to peer connection");
});
});
}
}
}
function answerer(){
peerConnection = new webkitRTCPeerConnection(cfg);
navigator.webkitGetUserMedia({'audio':true, video:true}, function (stream) {
console.log("Got local audio", stream);
video1.src = window.webkitURL.createObjectURL(stream)
peerConnection.addStream(stream);
},
function ( err ) {
console.log( 'error: ', err );
});
console.log("Answering");
//Listen for offer
signalingChannel.on('offer', function(data){
console.log("Offer Received");
//Set the remote description from caller's local description
peerConnection.setRemoteDescription(new RTCSessionDescription(JSON.parse(data)));
//Generate answer after getting the remote description
peerConnection.createAnswer(function(sessionDescription) {
//Set local description
peerConnection.setLocalDescription(sessionDescription);
//The local desc will be the answer sent back to offerer
signalingChannel.emit('answer', JSON.stringify(sessionDescription));
console.log("Answer sent");
});
});
}
function gotRemoteStream(event){
video2.src = window.webkitURL.createObjectURL(event.stream);
}
Here is a sequence of events I have working today (Feb 2014) in Chrome. This is for a simplified case where peer 1 will stream video to peer 2.
Set up some way for the peers to exchange messages. (The variance in how people accomplish this is what makes different WebRTC code samples so incommensurable, sadly. But mentally, and in your code organization, try to separate this logic out from the rest.)
On each side, set up message handlers for the important signalling messages. You can set them up and leave them up. There are 3 core messages to handle & send:
an ice candidate sent from the other side ==> call addIceCandidate with it
an offer message ==> SetRemoteDescription with it, then make an answer & send it
an answer message ===> SetRemoteDescription with it
On each side, create a new peerconnection object and attach event handlers to it for important events: onicecandidate, onremovestream, onaddstream, etc.
ice candidate ===> send it to other side
stream added ===> attach it to a video element so you can see it
When both peers are present and all the handlers are in place, peer 1 gets a trigger message of some kind to start video capture (using the getUserMedia call)
Once getUserMedia succeeds, we have a stream. Call addStream on the peer 1's peer connection object.
Then -- and only then -- peer 1 makes an offer
Due to the handlers we set up in step 2, peer 2 gets this and sends an answer
Concurrently with this (and somewhat obscurely), the peer connection object starts producing ice candidates. They get sent back and forth between the two peers and handled (steps 2 & 3 above)
Streaming starts by itself, opaquely, as a result of 2 conditions:
offer/answer exchange
ice candidates received, exchanged, and added
When I want to change the stream, I go back to step 3 and set up a new peer connection object and do the whole offer/answer again.
Why do you wait for ICE to complete before creating an answer? what about doing them simultaneously? That might help, as it is just meant to work simultaneously. If you can post your logs after this when it would still not work we can try debugging it even further. If you want to see an audio-only example of this (it sends both music-audio and microphone-audio) check here, and the github source. Server made with node.js and ws plugin. The audio connection works with webRTC.

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