Socket.io - Cannot send messages to rooms - javascript

socket.on('join room', function (data) {
var room = data.room;
var msg = "<span style='color:darkgreen;'>" + data.user + " has joined chat.</span>";
socket.join(room, function () {
console.log(socket.id + " now in rooms ", socket.rooms);
});
io.in(room).emit('system message', msg);
});
If I change io.in(room) to socket.broadcast the system message goes through. For whatever reason though, it will not send messages to specific rooms, it just does nothing, no errors either. Any ideas what's going wrong here?

It seems the code above is on back-end, correct?
Try the following, adapt it with your needs if necessary:
io.on('connection', function (socket) {
// You should expect ONLY one user connected,
// > otherwise, you're creating multiple sessions :)
console.log('a user connected');
socket.on('join room', function (data) {
var room = data.room;
console.log('Room: ', room);
var msg = "<span style='color:darkgreen;'>" + data.user + " has joined chat.</span>";
socket.join(room); // No callback needed
console.log(socket.id + " now in rooms ", socket.rooms);
io.to(room).emit('system message', msg);
// Change it to io.to(room)
});
});
I noticed you're asking to join on front-end using:
$(".channelChange").click( function(evt) {
var socket = io();
$("#messages").html("");
evt.stopPropagation();
if($.inArray(evt.currentTarget, $(this).children())){
console.log("user moved to " + evt.currentTarget.id);
}
setCookie("currentRoom", evt.currentTarget.id, 1);
$(".roomName").html("<span class='3Dtext'>" + evt.currentTarget.id + "</span>");
$("#enter-sound").get(0).play();
getMessages(getCookie("currentRoom"));
// Here you ask to join room
socket.emit('join room', { user: getCookie("username"), room: getCookie("currentRoom") });
})
And receiving the system message on:
$(function(){
var socket = io();
socket.on('system message', function(msg) {
alert("test");
$('#messages').append($('<div class="systemMessage">').html(msg));
var height = $("#messages")[0].scrollHeight;
$.mobile.silentScroll(height);
});
The main problem as you'll see, is that you're calling io() everytime, this created different sessions,
1. Session one joins a room
2. A different session will await for system message
Hence, you need a single io() session, a quick work arround is to make a self invoking function:
const setIo = (function (){
const _io = io();
return () => _io;
})()
And then Replace all io() declarations to: var socket = setIo();
such as:
$(".channelChange").click( function(evt) {
var socket = setIo();
$("#messages").html("");
evt.stopPropagation();
This will create a single io() session instead of different ones, and re-use it everytime you call setIo()
You'll eventually see the alert pop up:

socket.join() is asynchronous. It has not completed yet when you're trying to send to the room, so no message goes to that socket because it isn't yet in the room. So, change this:
socket.join(room, function () {
console.log(socket.id + " now in rooms ", socket.rooms);
});
io.in(room).emit('system message', msg);
to this:
socket.join(room, function () {
console.log(socket.id + " now in rooms ", socket.rooms);
io.in(room).emit('system message', msg);
});
So, in this new version you're not sending to the room until AFTER the .join() has completed rather than before.
You can see a similar example right in the socket.io doc here.

Related

Using rooms with socket.io

In the socket.io documentation I see an example of rooms
io.on('connection', function(socket){
socket.on('say to someone', function(id, msg){
socket.broadcast.to(id).emit('my message', msg);
});
});
I have a route /rooms/:roomId.
Is it possible to make the sockets being sent between the server and the client only hits the specific room?
I guess the server should be something like
io.on('connection', function(socket){
socket.on('new msg from client', function(roomId, msg){
io.to(id).emit('new msg from server', msg);
});
});
above and the client should send messages with
socket.emit('new msg from client', roomId, msg);
and get new messages simply with
socket.on('new msg from server', function () {
document.getElementById('msgs').appendChild(...);
});
But will this work? Shouldn't I join the room with socket.join(...) before I can do this?
For a haiku sharing application I made I have something like this:
io.on('connection', function(socket) {
var socket_id = socket.id;
var client_ip = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address.address;
clients.push(socket);
console.info('New client connected (id=' + socket.id + ').');
number_of_clients_connected++;
console.log('[' + client_ip + '] connected, ' + number_of_clients_connected + ' total users online.');
//when a socket is disconnected or closed, .on('disconnect') is fired
socket.on('disconnect', function() {
number_of_clients_connected--;
console.log('[' + client_ip + '] disconnected, ' + number_of_clients_connected + ' total users online.');
//on disconnect, remove from clients array
var index = clients.indexOf(socket);
if (index != -1) {
clients.splice(index, 1);
//console.info('Client gone (id=' + socket.id + ').');
}
});
So it keeps an array of clients and when certain messages need relaying you can specify the client socket ID...
//reads from latest_haikus_cache and sends them
socket.on('request_haiku_cache', function() {
latest_haikus_cache.forEach(function(a_latest_haiku) {
clients[clients.indexOf(socket)].emit('load_haiku_from_cache', a_latest_haiku);
});
});
The server is allowed to broadcast to any room so you can support letting a client ask the server to send to a room without that client being in the room. It really depends upon what you want to do.
So, if you want your server to have this code that lets any client send a message to any room of their choosing:
io.on('connection', function(socket){
socket.on('new msg from client', function(roomId, msg){
io.to(roomId).emit('new msg from server', msg);
});
});
Then, you can indeed do that and it will work. Whether it's appropriate or not is entirely up to your application and whether you want any client to be able broadcast to any room that it has the name of.
But will this work?
Yes, it will work.
Shouldn't I join the room with socket.join(...) before I can do this?
There is no need to have the client join the room unless it wants to receive messages for that room. You don't have be in the room in order to ask the server to send to that room if that's how you want to do it. So, all this is entirely up to your application and what is appropriate.
I have a route /rooms/:roomId.
Is it possible to make the sockets being sent between the server and
the client only hits the specific room?
I could not figure out what this part of your question means. If you care to explain further, I will try to help with this part too.

Socketio 1.0 get attributes from all clients

I am currently working on a simple socketio application in which i am sending some parameters to envents and i am storing them into each socket.
socket.on('add user', function (data) {
if (addedUser) return;
// we store the username in the socket session for this client.
socket.nickname = data.nickname;
socket.userId = data.userId;
...
Then i am getting all socketio clients using var io.engine.clients and i am trying to obtain that parameters in other event like this:
socket.on('new message', function (data) {
var clientsRoom = io.engine.clients;
for(var c in clientsRoom){
console.log(" Client: " + c.userId); //Error
console.log(" Client: " + c); //outputs the socket ID
console.log(" Client: " + c['userId']); //Error
}
...
but i am unable to get my userID previously stored for all sockets. ¿What am i doing wrong?
Thanks.
Instead of io.engine.clients, you should use io.sockets.sockets (they aren't the same, socket.io adds an extra layer on top of engine.io). Also make sure that you treat it as an object, not an array:
var clientsRoom = io.sockets.sockets;
for (var id in clientsRoom) {
var c = clientsRoom[id];
console.log(" Client: " + c.userId);
console.log(" Client: " + id);
}

node.js / socket.io - keep track of clients

Dear friends I have a small issue while trying to keep track of a logged in users in chat. The chat is based on two separate channels that work with the help of namespaces:
chatInfra - to handle logged in users and send welcome messages.
chatCom - to handle messageing between users.
I have searched a lot but I found only theoretical explanations that the best solutions is to store users into array. Therefore I tried to keep the track of logged in users by storing them in array and then iterating through them but still the result is not good.
The problem is that after entering to the chat, only the first logged in user's name appears on screen, whereas the second user's name is not visible.
This is my server side code, I am trying to store users into clients array:
var clients = [];
var chatInfra = io.of("/chat_infra").on("connection", function(socket){
socket.on("set_name", function (data) {
clients.push(data.name);
socket.emit('name_set', data);
socket.send(JSON.stringify({
type:'serverMessage',
message:'Welcome!'
}));
socket.broadcast.emit('user_entered', data);
});
});
var chatCom = io.of("/chat_com").on("connection", function (socket) {
socket.on('message', function (message) {
message = JSON.parse(message);
for(var key in clients){
if(message.type == "userMessage"){
message.username = clients[key];
console.log('message : ', message);
socket.broadcast.send(JSON.stringify(message));
message.type = "myMessage";
socket.send(JSON.stringify(message));
}
}
});
});
Here is how it looks in browser: http://screencast.com/t/lshnfcGZ8E8
Here is the full code: https://gist.github.com/johannesMatevosyan/0b9f7e588338dbb6b7f5
I think you're creating an unnecessary overkill by using different namespaces. Here's a clearer working example achieving the same functionality:
server.js
var app = require("express")();
var server = require("http").Server(app);
var io = require("socket.io")(server);
var chat = io.of("/chat").on("connection", function(socket){
socket.on("set_name", function (data) {
socket.username = data.name;
socket.emit("name_set", data);
socket.emit("message", {
type :"serverMessage",
message :"Welcome!"
});
chat.emit("message", {
type :"serverMessage",
message : data.name + " has joined the room.!"
});
});
socket.on("message", function (message) {
message.username = socket.username;
chat.emit("message", message);
});
});
app.get("/", function (req, res) {
res.sendfile(__dirname + "/index.html");
});
server.listen(3000);
client.js
var socket = io.connect('http://localhost:3000/chat');
socket.on('name_set', function (data) {
$('#nameform').hide();
$('#messages').append('<div class="systemMessage">Hello ' + data.name + '</div>');
});
socket.on('message', function (message) {
var userNameHtml = message.username ? '<span class="name">' + message.username + ':</span>' : '';
$('#messages').append('<div class="' + message.type + '">' + userNameHtml + message.message + '</div>');
});
$(function () {
$('#setname').click(function () {
socket.emit("set_name", { name: $('#nickname').val() });
});
$('#send').click(function () {
socket.emit("message", {
message : $('#message').val(),
type : 'userMessage'
});
$('#message').val('');
});
});
I don't think you need a separate event handler for user_entered, since you are treating it as a regular message and not doing anything else with the event. Also a couple of things:
You don't need to first connect to the server and then to the namespace address, connecting to the later is just fine.
Don't set event listeners within callbacks, that will result in setting them multiple times.

Duplicate Events Socket.io and Node.js over STOMP

I need some help about my node.js+socket.io implementation.
This service expose a server that connects to an ActiveMQ broker over the STOMP protocol, using the stomp-js node.js module to receive events; that then are displayed in a web front end through websockets using socket.io.
So, everything was fine until I started use the Filters feature of ActiveMQ, but this was not the failure point because of my and my team researching, we found the way to ensure the implementation was fine, the problem comes with the connections: So here's the thing, I receive the filters to subscribe, I successfully subscribe to but when I receive a new set of filters is when comes the duplicated, triplicated and more and more messages depending the number of times that I subscribe-unsubscribe to.
So making some debug, I cannot see what's the problem but I'm almost sure that is some bad implementation of the callbacks or the program flow, I'll attach my code to read your comments about it.
Thanks a lot!
var sys = require('util');
var stomp = require('stomp');
var io = require('socket.io').listen(3000);
var socket = io.sockets.on('connection', function (socket) {
var stomp_args = {
port: 61616,
host: 'IP.ADDRESS',
debug: true,
};
var headers;
var client = new stomp.Stomp(stomp_args);
var setFilters = false;
socket.on('filtros', function (message) {
console.log('DEBUG: Getting filters');
if(setFilters){
client.unsubscribe(headers);
}
else{
client.connect();
}
var selector = '';
headers = '';
for(var attributename in message){
console.log(attributename+" : " + message[attributename]);
if(message[attributename] != ''){
selector += ' ' + attributename + '=\'' + message[attributename] + '\' AND ';
}
}
selector = selector.substring(0, selector.length - 4)
console.log('DEBUG: Selector String: ' + selector);
headers = {
destination: '/topic/virtualtopic',
ack: 'client',
selector: selector
};
if(setFilters)
client.subscribe(headers);
client.on('connected', function() {
client.subscribe(headers);
console.log('DEBUG: Client Connected');
setFilters = true;
});
});
var bufferMessage;
client.on('message', function(message) {
console.log("Got message: " + message.headers['message-id']);
var jsonMessage = JSON.parse(message.body);
if(bufferMessage === jsonMessage){
console.log('DEBUG: recibo un mensaje repetido');
return 0;
}
else{
console.log('DEBUG: Cool');
socket.emit('eventoCajero', jsonMessage);
}
client.ack(message.headers['message-id']);
bufferMessage = jsonMessage;
});
socket.on('disconnect', function(){
console.log('DEBUG: Client disconnected');
if(setFilters){
console.log('DEBUG: Consumer disconnected');
client.disconnect();
}
});
client.on('error', function(error_frame) {
console.log(error_frame.body);
});
});
Looking in the Socket.IO documentation, I've found that this is a known issue (I think critical known issue) and they have not fixed it yet. So, to correct this is necessary to reconnect to the socket in the client side to avoid duplicate messages, using:
socket.socket.reconnect();
function to force reconnection explicitly.

How do I store persistent data with xhr-polling in Socket.IO?

I'm writing a multi-room chat app with Node.js and Socket.IO and I'm wondering how to handle data that needs to persist for the duration of a session, when the transport is xhr-polling.
Currently, on the client I'm sending a "join" event on connection, which passes a long a few variables (such as username), which I store server-side using Socket.IO's get and set methods. They are then available until the client disconnects.
This works fine if the transport is Websockets, but if it falls back to xhr-polling, the join event is emitted on each connect, which happens every 5-10 seconds. (Similarly, "such and such has left/joined the room" is sent on each xhr-poll request, which is also undesirable.)
I'm not sure what the best way forward is. I can't disable xhr-polling as it's a needed fallback for flashless IE for one.
Here is the relevant client code:
socket.on("connect", function(){
socket.emit("join", { username: username, room: room });
});
And on the server:
var io = require("socket.io").listen(8124)
, buffer = {}
, max_room_buffer = 15;
io.sockets.on("connection", function(socket) {
socket.on("join", function(data){
if(data.username && data.room) {
socket.set("data", data);
socket.join(data.room);
if(!buffer[data.room]) buffer[data.room] = [];
socket.broadcast.to(data.room).emit("message", "<em>" + data.username + " has joined room " + data.room + "</em>");
socket.emit("message", "<em>You have joined room " + data.room + "</em>");
socket.emit("message", buffer[data.room]);
}
});
socket.on("message", function(message) {
if(message) {
socket.get("data", function(err, data){
if(data) {
var msg = "<span>" + data.username + ":</span> " + message;
buffer[data.room].push(msg);
if(buffer[data.room].length > max_room_buffer) buffer[data.room].shift();
io.sockets.in(data.room).emit("message", msg);
}
});
}
});
socket.on("disconnect", function () {
socket.get("data", function(err, data){
if(data) {
socket.leave(data.room);
socket.broadcast.to(data.room).emit("message", "<em>" + data.username + " has left room " + data.room + "</em>");
}
});
});
});
Thanks in advance.
Perhaps I'm missing something, but wouldn't this work.
Client:
var firstJoin = true;
socket.on("connect", function(){
socket.emit("join", { username: username, room: room, firstJoin: firstJoin });
firstJoin = false;
});
Server:
io.sockets.on("connection", function(socket) {
socket.on("join", function(data){
if(data.username && data.room) {
socket.set("data", data);
socket.join(data.room);
if(!buffer[data.room]) buffer[data.room] = [];
if (data.firstJoin) {
socket.broadcast.to(data.room).emit("message", "<em>" + data.username + " has joined room " + data.room + "</em>");
socket.emit("message", "<em>You have joined room " + data.room + "</em>");
socket.emit("message", buffer[data.room]);
}
}
});
As for the fact that the connection event occurs on every new poll, that's an unavoidable consequence of XHR polling. Since each poll is a new HTTP request, there is no state from the previous request, so you need to set it up again.
If you want to reduce how frequently this happens, you could try increasing the polling duration from its default of 20. For example:
io.configure(function() {
io.set('polling duration', 60);
});

Categories