I am using the ws lib and I want to make a private chat just like this: client A sends a message to client B.
There I have a ws.clients.forEach() method to broadcast to every client, but how can I get the client id of an individual one?
When you set up your chat system and userA wants to chat with userB, you have to have an identifier that userA users to tell the server they want to chat with userB. If the ws library doesn't provide an easy to use identifier for each connected user, then you need to assign one when the user connects to your server and then keep track of it for each connection in a way you can find the socket that has a given id. One simple way would be to just create a Map object where key is the ID and value is the socket. You can then lookup any socket by id.
For example, you could create a unique ID for each incoming socket connection and store it like this:
const uuid = require("uuid/v4");
const wss = new WebSocket.Server({ port: 8080 });
const idMap = new Map();
wss.on('connection', function connection(ws) {
// create uuid and add to the idMap
ws.uuid = uuid();
idMap.add(ws.uuid, ws);
ws.on('close', function() {
// remove from the map
idMap.delete(ws.uuid);
});
ws.on('message', function(info) {
try {
info = JSON.parse(info);
if (info.action = "send") {
let dest = idMap.get(info.targetId);
if (dest) {
dest.send(JSON.stringify({action: "message", sender: ws.uuid, data: info.message}));
}
}
} catch(e) {
console.log("Error processing incoming message", e);
}
});
});
Then, you could use that uuid value internally to identify which user someone wanted to connect to or send to.
In most real applications, you will also create some sort of login and username. A unique username could also be used as the id.
Related
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.
I am building a web socket application using node and i want to store my currently connected users ,so i saved it as a user Id key and socket Id value but when the user disconnect i only knows it's socket Id so i will not be able to remove it from Redis (since Redis only allow search by key) so how would i solve it ? i tried to reverse the key and value but it's impossible as a socket id is not unique
const isUserExist = async (socketId) => {
return await redisClient.get(socketId);
};
const addUser = async (socketId, userId) => {
if (await isUserExist(socketId)) return console.log("user already exist");
await redisClient.set(socketId, userId);
console.log("user is set inside database ");
};```
i have actually discovered that socket object carries the socket id of the current connected user , so i used it and my problem was solved and we also need to store the id as the key and the socket id as the value
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!
I am adding the username to the socket object like this which is working fine
socket.on('add user', function (username) {
socket.username = username;
});
Lets assume the username is khawer and now i want to emit to this socket where username is khawer but i am unable to do so.
I have tried this
io.sockets.connected[socket.username].emit('chat message', msg);
And this
io.sockets.sockets[socket.username].emit('chat message', msg);
But both did not work. What am i doing wrong here?
Just assigning a username property to a socket does not make it so that it's indexed by name - thus you cannot do either of the types of lookups you're doing.
If you want to find a socket by username, you will either have to do a brute force search of all the sockets to find the one that has the same user name or you will have to create your own index of sockets by name.
If you want to do a brute force lookup to find it, you could do this:
var list = io.sockets.sockets;
for (var i = 0; i < list.length; i++) {
if (list[i].username === "khawer") {
list[i].emit('chat message', msg);
}
}
You could also put each user into a chatroom with a name that matches their username. Then, you could send to any given username by simply sending to the chatroom by that name. You'd be using the chatroom feature as an index by username. It would just require one extra step to put a socket into a chatroom that matches their username when they connect.
Or, each time a socket connects and disconnects, you could maintain your own socket index by username (this is relatively common).
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.