Maintain socketio conection to each new tab opened by the same user - node.js

I'm making an application for our website where users can send chat messages with each other. I've managed to do this successfully using nodejs and socketio. I have a header with some sort of notification icon just like facebook that can be seen in all the pages. Now if the user opens multiple tabs and he receives a message, then all open tabs should see the icon light up. I've gotten this to work by tracking the socket connections opened by users through a 2D array of sockets:
var container = {};
io.sockets.on( 'connection', function(client) {
client.on('set account online', function(username) {
if (!(username in container)) {
console.log(username + " is now Online" );
container[username] = [];
}
client.username = username;
container[username].push(client);
});
client.on('set account offline', function(username) {
if (username in container) {
delete container[username];
console.log(username + " is now Offline" );
}
});
And then when a message is sent I iterate through the appropriate array element
client.on('send message', function(data) {
if (data.recipient in container) {
var clients = container[data.recipient];
for(var i = 0; i < clients.length;i++){
clients[i].emit('send message', {recipient: data.recipient, message: data.message });
}
}
});
That's working well and all (not sure how well coded it is though). The problem is if the user closes a tab, the socket for that tab still exists in the container variable and node would still try to emit to that socket if a message is received for that particular user. Also it just feels cleaner to un-track any disconnected socket.
I've been thinking about this and I think I have to tie the socket disconnect event to the client side's onbeforeunload event and we all know how that performs across different browsers. Any suggestion regarding what's the proper way to splice off disconnected sockets from the container array?

As per my comment:
You should really be implementing rooms. On each connection each user
should join their own room, any additional connections from the same
user should join this room. You can then emit data to the room and
each client inside it will receive the data.
Your code can be changed to:
io.sockets.on('connection', function(client) {
client.on('set account online', function(username) {
client.join(username);
});
client.on('set account offline', function(username) {
client.leave(username);
});
client.on('send message', function(data) {
io.to(data.recipient).emit('send message', {
recipient: data.recipient,
message: data.message
});
});
});

Related

how to send typing alert and message to users in a particular room using nodejs socket.io

The code below works fine by broadcasting typing notification and chat messages to all connected users.
Here is what I want: How do I send typing notification and chat messages only to users connected to a particular room
say Room1, Room2 etc.
here is the code
index.html
var socket = io();
var user = 'nancy';
function submitfunction(){
var from = 'nancy';
var message = 'hello Nancy';
socket.emit('chatMessage', from, message);
}
function notifyTyping() {
var user = 'nancy'
socket.emit('notifyUser', user);
}
socket.on('chatMessage', function(from, msg){
//sent message goes here
});
socket.on('notifyUser', function(user){
$('#notifyUser').text(nancy is typing ...');
setTimeout(function(){ $('#notifyUser').text(''); }, 10000);
});
server.js
var io = require('socket.io')(http);
io.on('connection', function(socket){
socket.on('chatMessage', function(from, msg){
io.emit('chatMessage', from, msg);
});
socket.on('notifyUser', function(user){
io.emit('notifyUser', user);
});
});
am using npm installed socket.io ^2.3.0
To send the message to a specific room, you will have to create and join a room with roomId. Below is a basic code snippet
//client side
const socket = io.connect();
socket.emit('create', 'room1');
// server side code
socket.on('create', function(room1) {
socket.join(room1);
});
To emit data to a specific room
// sending to all clients in 'room1'except sender
socket.to('room1').emit('event',data);
// sending to all clients in 'room1' room, including sender
io.in('room1').emit('event', 'data');
You can follow this question for details on how to create a room?
Creating Rooms in Socket.io
This emit cheat sheet might also be useful:
https://github.com/socketio/socket.io/blob/master/docs/emit.md

Expose socket.io listeners after client action

Is there a way to set up socket.io listeners for certain clients after they execute a command? For example:
socket.on('setupServer', function (data) {
console.log("setupServer called");
// Now set up listeners
socket.on('serverAction', function (data) {
console.log('Listener for clients calling setupServer');
});
});
In the above, a client has connected and has issued a 'setupServer' command to the node.js server. The server now listens for 'serverAction' from the specific client. Other clients won't be able to use 'serverAction' without calling 'setupServer' first.
You could create a room and validate some data from user and then join those users to that room, after that emit some events to that users in that room.
Something like this.
server.js
var io = require('socket.io')(server);
io.on('connection',function(socket){
socket.emit('auth_user')
socket.on('response_from_user',function(data){
if(data.name === "Blashadow"){
//join that user to the room
socket.join('/room',function(){
//emit some custom event to users in that room
io.in('/room').emit('custom_event_room', { info: 'new used connected from room' });
});
}
});
});
client.html
var socket = io('localhost');
socket.on('auth_user', function (data) {
socket.emit('response_from_user', { name : "Blashadow" });
});
socket.on('custom_event_room',function(data){
console.log(data);
});

XMPP MUC how to permanently join a room

I want the bot owner of the XMPP room to be persistently present, but I keep disappearing from the room and have to rejoin. What must I do to maintain my presence in a room? Is it configurable? I cannot find an answer in XEP-0045.
http://xmpp.org/extensions/xep-0045.html
Here is my code:
function daemonPresence(callback) {
var ElizaBot = require('./eliza');
var eliza = new ElizaBot();
var initial = eliza.getInitial();
var XMPP = require('stanza.io');
var administrator = 'metalaureate#' + config.get('xmpp.domain');
var client = XMPP.createClient({
jid: administrator,
password: 'password',
transport: 'bosh',
boshURL: config.get('xmpp.bosh_url')
});
client.enableKeepAlive();
client.on('session:started', function () {
console.log(administrator + ' is sending presence');
client.joinRoom("architecture#groups.xxxx.xxx", 'Daemon');
setInterval(function () {client.sendPresence();console.log('daemon presence');},60000);
client.on('chat', function (msg) {
console.log(msg.body);
var reply = eliza.transform(msg.body);
client.sendMessage({
to: msg.from,
body: 'hello world' // 'You sent: ' + msg.body
});
});
client.on('groupchat', function (msg) {
console.log('group chat', msg.body);
});
});
client.on('session:end', function (result) {
console.info("daemon session ended, restarting");
setTimeout(function () {
daemonPresence();
}, 10000);
// callback(null, result);
});
client.on('session:error', function (error) {
console.err('xmpp error', error);
callback(error, null);
});
client.connect();
}
This is the nature of XMPP Multi User Chat as defined in XEP-0045. XMPP MUC room are presence based. It means you need to send your presence to the MUC every time you login. This is what is defined in the protocol.
Some client work around this by implementing bookmarks as XML private storage to store a list of MUC room the client will auto join on connect you may want to look into this.
The XMPP Standards Foundation is discussing building a new MUC specification (aka MUC 2) that will not be coupled to presence. However, this is just a discussion at the moment.

socket.io send packet to sender only

I have yet to figure out how to directly respond to only the sender using socket.io
I have learned that io.sockets.emit sends to all clients but I wont to send information back to the sender.
code:
socket.on('login', function (data) {
db.users.find({username: cc.lowerCase(data.username)}, function(err, users) {
if (users.length > 0) {
users.forEach( function(user) {
console.log(user.length);
if (user.password == data.password) {
io.sockets.emit('login', { username: user.username });
} else {
io.sockets.emit('error', { message: "Wrong username or password!" });
}
});
} else {
io.sockets.emit('error', { message: "Wrong username or password!" });
}
});
});
When your server listens, you usually get a socket at the "connection" event :
require('socket.io').on('connect', function(socket){
A socket connects 2 points : the client and the server. When you emit on this socket, you emit to this specific client.
Example :
var io = require('socket.io');
io.on('connect', function(socket){
socket.on('A', function(something){
// we just received a message
// let's respond to *that* client :
socket.emit('B', somethingElse);
});
});
Be careful that those are two different calls :
socket.emit : emit to just one socket
io.sockets.emit : emit to all sockets
Simple example
The syntax is confusing in socketio. Also, every socket is automatically connected to their own room with the id socket.id (this is how private chat works in socketio, they use rooms).
Send to the sender and noone else
socket.emit('hello', msg);
Send to everyone including the sender(if the sender is in the room) in the room "my room"
io.to('my room').emit('hello', msg);
Send to everyone except the sender(if the sender is in the room) in the room "my room"
socket.broadcast.to('my room').emit('hello', msg);
Send to everyone in every room, including the sender
io.emit('hello', msg); // short version
io.sockets.emit('hello', msg);
Send to specific socket only (private chat)
socket.broadcast.to(otherSocket.id).emit('hello', msg);
late but better than never ;)
I had smth like this:
io.on('connection', function(socket){
socket.emit('some event', data);
socket.on('private event', function(message){
this.emit('other private event', message);
}
}
After some testing, I realized that 'this.emit' inside 'private event' closure send back only to sender. So i tried 'this.broadcast.emit', which send the message to all connected except the sender. I believe this is what you want from your code.

Having trouble managing multiple sockets [duplicate]

I'm having trouble getting basic client to client (or really client->server->client) working with socket.io. Heres the code I have right now:
io.sockets.on('connection', function (socket) {
users.push(socket.sessionId);
for(userID in users) {
console.log(userID);
io.sockets.socket(userID).emit('message', { msg: 'New User Connected succesfully' });
}
socket.emit('message', { msg: 'Connected succesfully' });
socket.on('my other event', function (data) {
console.log(data);
});
});
From my understanding, that should send the new user message to every connected user (individually, since i want to do actual individual messages later). Instead, I only get the 'connected successfully' message at the end. I don't get any errors or other negative indicators from my server or client.
Any ideas of why io.sockets.socket(userID).emit() doesn't work or what to use in its place?
Socket.io has the concept of rooms where, once a socket has joined a room, it will receive all message sent to a room, so you don't need to track who's in the room, deal with disconnections, etc...
On connection, you'd use:
socket.join('room')
And to send a message to everyone in that room:
io.sockets.in('room').emit('event_name', data)
More info on the socket.io wiki: https://github.com/LearnBoost/socket.io/wiki/Rooms
Try
users.push(socket); // without .sessionId
for (var u in users) {
// users[u] is now the socket
console.log(users[u].id);
users[u].emit('message', { msg: 'New User Connected succesfully' });
}
You can now also use ...
io.to('room').emit('event_name', data);
as an alternative to io.sockets.in

Resources