Socket.io message event firing multiple times - javascript

I was trying to learn node and started creating a mashup with socket.io
The message transportation have begin but I have run into some trouble.
The message event is firing multiple times leading to a single message appearing multiple times on the recipient's box. I have routed the socket to exports.chat and was wondering if that is causing the problem?
To narrow down the problem: the messages are firing the number of times = the sequence of connection of the client. That is, if a client connects second, his messages will fire twice. three times for the client connecting third.
Here is the code snippet:
exports.chat = function(io, pseudoArray, req, res){
res.render('chat', {title: 'ChatPanel.'});
var users = 0;
io.sockets.on('connection', function (socket) { // First connection
users += 1;
// reloadUsers(io, users);
socket.on('message', function (data) { // Broadcast the message to all
if(pseudoSet(socket)) {
var transmit = {date : new Date().toISOString(), pseudo : returnPseudo(socket), message : data};
socket.broadcast.emit('message', transmit);
console.log("user "+ transmit['pseudo'] +" said \""+data+"\"");
}
});
socket.set('pseudo', req.session.user, function(){
pseudoArray.push(req.session.user);
socket.emit('pseudoStatus', 'ok');
console.log("user " + req.session.user + " connected");
});
socket.on('disconnect', function () { // Disconnection of the client
users -= 1;
// reloadUsers();
if (pseudoSet(socket)) {
var pseudo;
socket.get('pseudo', function(err, name) {
pseudo = name;
});
var index = pseudoArray.indexOf(pseudo);
pseudo.slice(index - 1, 1);
}
});
});
};

The whole part of socket.io code has to go outside external.chat function. Socket IO has to bind with the http/app server, you should not handle it within each request.
the messages are firing the number of times = the sequence of connection of the client
What essentially happening is, each time a new request arrives you are registering a event handler for message, hence it is fired as many times as the you have accessed chat URL.
io.socket.on('message', function (data) {...})

So I had the same problem. The solution is to close all your listeners on the socket.on('disconnect') event, this is what my code looks like -
socket.on('disconnect', function () {
socket.removeAllListeners('send message');
socket.removeAllListeners('disconnect');
io.removeAllListeners('connection');
});
Might not need to call it on disconnect, not sure but I do it anyway.

I think this misbehavior is because you are attempting to use one of the handful of built-in/reserved event names "message" as an application-specific message. To confirm, change your event name to "message2" or something else and see if the problem goes away. I believe at least "connect", "disconnect", and "message" are reserved. https://github.com/LearnBoost/socket.io/wiki/Exposed-events

Link: https://socket.io/docs/v3/listening-to-events/#socketoffeventname-listener
Please use socket.off method to removes the specified listener from the listener array for the event named eventName.
socket.off("message").on("message", this.UpdateChat);

Restarting the server can be responsible for several identical event listeners on the client side. If the client has not reloaded (restarted) you have to make sure that the old event listeners are deleted on the client side when establishing a new connection. You can do that with
io.socket.removeAllListeners()

SOCKET.IO v3.x
I don't really know why reserve event in socket.io are firing up multiple times, hence I make a logic that fits to our needs and address this problem,
I simply create a global mutable value that could change everytime the 'disconnect' fire up, here's the logic I did
let ACTIVE_USERS = []; //assume the propery of an object is { name, socket_id }
const connections = (socket) => {
socket.on("disconnect", (reason) => {
//lets check how many times it fires up
console.log('YOW', reason);
//mutate the ACTIVE_USERS
ACTIVE_USERS = ACTIVE_USERS .filter(user => {
//lets figure it out if this thing helps us
if(user.socket_id === socket.id){
console.log('HEY!!!');
socket.broadcast.emit('disconnected-user',[
message: `${user.name} has been disconnected`
})
}
})
});
}
and the result of it is here

Related

Socket IO Rooms: Trying to use rooms in a game app (react-native) so multiple groups of people can play the game independently of each other

I have an app where multiple people join a game by entering a code that is generated on the phone hosting the game. I want to use this code as the name of a socket io room, so multiple games can be going on between different groups of players.
Here is my server code:
var express = require('express');
var app = express();
var server = require('http').Server(app);
var io = require('socket.io')(server);
server.listen(3000);
io.on('connection', function (socket) {
//passing in the game_code from client side and using it as the room name
socket.on('names', function (game_code) {
socket.join(game_code);
io.to(game_code).emit('names')
});
socket.on('round_start', function (game_code) {
io.to(game_code).emit('round_start')
});
socket.on('game_start', function (game_code) {
io.to(game_code).emit('game_start')
});
socket.on('end_round', function (game_code) {
io.to(game_code).emit('end_round')
});
socket.on('next_round', function (game_code) {
io.to(game_code).emit('next_round')
});
socket.on('end_game', function (game_code) {
io.to(game_code).emit('end_game')
});
});
The 'names' socket is for players entering their name before the game starts, and the rest of them are for transitions to the next screen; one phone, usually the host, presses a button that causes all the phones to go to the next screen. The 'names' socket is working correctly, and so is the 'round_start' socket, which is the first screen transition. The next screen transition after this does not work.
All of the screen transitions work if I don't use rooms, so I'm pretty sure my react-native code isn't the problem here. It must be something wrong with the server code I've shown above.
Since you didnt provide full source, I can only assume what might went wrong.
First of all, since you used io.to(game_code).emit('names') I assume, you want the 'names' event to be sent to all clients in the room game_code, including the sender.
(Sidenote: If you want this event to be sent to all users in the room, except the sender, you should have used socket.to(game_code).emit('names'). See https://socket.io/docs/emit-cheatsheet/ )
But, since the .join method is asynchronous, the 'names' event may gets fired, before the client joined the room. So the sender never receives the 'names' event fired by himself, only the 'names' events, fired by other clients.
To ensure, the 'names' event gets fired after the client joined the room, you can use a callback for the .join method: socket.join(room, callback).
io.on('connection', function (socket) {
//passing in the game_code from client side and using it as the room name
socket.on('names', function (game_code) {
socket.join(game_code, (game_code) => io.to(game_code).emit('names'););
});
//rest of your code
});
In case you're unfamiliar with the => arrow function , (game_code) => io.to(game_code).emit('names') is short for
function (game_code){
return io.to(game_code).emit('names');
}
(nevermind the return keyword, it's just part of the arrow function)

How can I detect disconnects on socket.io?

I am using socket.io in my project. I turned on reconnect feature. I want to if user disconnects from server show an alert (Your internet connection loss. Trying reconnect). And if the user reconnects again I want to show one more alert (Don't worry, you are connected).
How can I do it?
To detect on the client you use
// CLIENT CODE
socket.on('disconnect', function(){
// Do stuff (probably some jQuery)
});
It's the exact same code as above for a node.js server too.
If you want for some reason to detect a user disconnecting and display it to the others, you will need to use the server one to detect it the person leaving and then emit back out a message to the others using something like:
socket.on('disconnect', function(){
socket.broadcast.to(roomName).emit('user_leave', {user_name: "johnjoe123"});
});
Hope this helps
socket.io has a disconnect event, put this inside your connect block:
socket.on('disconnect', function () {
//do stuff
});
I handled this problem this way. I've made an emit sender on client which is calling heartbeat on server.
socket.on('heartbeat', function() {
// console.log('heartbeat called!');
hbeat[socket.id] = Date.now();
setTimeout(function() {
var now = Date.now();
if (now - hbeat[socket.id] > 5000) {
console.log('this socket id will be closed ' + socket.id);
if (addedUser) {
--onlineUsers;
removeFromLobby(socket.id);
try {
// this is the most important part
io.sockets.connected[socket.id].disconnect();
} catch (error) {
console.log(error)
}
}
}
now = null;
}, 6000);
});
I found this code function to call:
io.sockets.connected[socket.id].disconnect();
inpired on invicibleTrain iv made a online checker also
SERVER SIDE
const sockets={online:{},admins:{}}
// the disconnect function you create the way you wish to, example:
const disconnect=()=>{
socket.emit('disconected')
io.in(socket.id).disconnectSockets()
delete sockets.online[socket.uid]
}
io.on('connection',socket=>{
socket.emit('connection',socket.id)
// the checker if user is online
let drop
const dropCheck=()=>{
if(!socket) return; // if user connects twice before the check, simply stops process
socket.emit('dropCheck')
drop = setTimeout(()=>disconnect(),4000) // 4 secs to recieve answer
}
const setDrop=()=>setTimeout(()=>dropCheck(),60000) // 60 secs to restart the process
socket.on('dropCheck',()=>{
clearTimeout(drop) // cancells actual drop coutdown (if any)
setDrop() // sets a new
})
//sets the user ID inside the socket used for comunication
socket.on('uid',uid=>{
socket.online[uid] = {status:'busy',socket:socket.id}
setDrop()
})
})
CLIENT SIDE
const sio = io(`...httpAddress...`,{transports:['websocket']})
sio.on('connection',socket=>{
sio.emit('uid','user Id here..') // sends the the user ID: uid
sio.on('dropCheck',()=>{ // responds to the checker
sio.emit('dropCheck')
})
})
Explanation:
The user logs in and after 60 seconds dropCheck is called
the dropCheck emits a ping and set a timmer of 4 seconds
the user handle the emited ping and responds with another ping
a: if the response comes within 4 secs the 4 secs timmer is cancelled and refreshes the 60 seconds timmer (restarting the
process)
b: if the response fails or delay too much the disconnect() function is called

Using Node and Socket.io I am trying to pass a simple array and I'm getting nothing. What did I miss?

I followed the net tuts tutorial to build a simple chat application with socket and node, and now I'm trying to extend the app to allow people to play a game I've written, so I want to let people list the games available, but I can't seem to pass even a simple test array from server to client.
I'll let the code speak for itself:
Relevant Server Code:
var games = ["test"];
io.sockets.on('connection', function (socket) {
socket.emit('message', { message: 'welcome to the chat' });
socket.on('gameList', function (data) {
io.sockets.emit("games",games);
});
socket.on('send', function (data) {
io.sockets.emit('message', data);
});
});
ClientSide:
window.games = [];
$("#listGames").click(function(){
socket.emit("gameList", function(data){
window.games = data;
})
})
So I console.log games before and after the button click, and it's always just an empty array, when in my head, it should contain the string "test".
So where did I go wrong?
Note:
Added jQuery to the codebase even though the tutorial missed it.
You are using socket.emit() to attempt to receive data. That only sends data to the server. You need a games event handler on your client to handle the event accordingly.
window.games = [];
$("#listGames").click(function() {
socket.emit('gameList');
});
socket.on('games', function (games) {
window.games = games;
});
The event handler for games is what will fire when you execute io.sockets.emit('games', games); on the server side.
Also make sure to always pass an object as the response over Socket.IO. Change the server-side code from : var games=[]; to var games={'games':[]}; or something similar.

How can I prevent malicious use of my sockets?

I'm making a webpage based around players being able to invite other players to parties, and other things a long the lines.
I have your basic send / receive / update of the chat/users in your party. The only thing is, what's to stop somebody from sitting there opening up a developer console and going
socket.emit('updateUsers', 'Weiner');
socket.emit('updateUsers', 'Idiot');
socket.emit('updateUsers', 'Bad word');
socket.emit('updateUsers', 'Other stupid malicious really long spam the chat name');
How can I prevent against this so that they can not do such things?
(Full JS Stack, Node.js)
Thanks!
I faced this problem aswell, This was my solution as far as spamming emits go (malicious socket use)..
var spamData = new Object();
var spamCheckFunctions = ["updateUsers","moreEmits"]; // anti-spam will check these socket emits
var antiSpam = 3000; // anti spam check per milliseconds
var antiSpamRemove = 3; // -spam points per antiSpam check
var maxSpam = 9; // Max spam points before disconnect is thrown to the socket
io.sockets.on('connection', function (socket) {
// Spam Check, this binds to all emits
var emit = socket.emit;
socket.emit = function() {
data = Array.prototype.slice.call(arguments);
if(spamCheckFunctions.contains(data[0])){
addSpam(socket);
};
emit.apply(socket, arguments);
};
var $emit = socket.$emit;
socket.$emit = function() {
data = Array.prototype.slice.call(arguments);
if(spamCheckFunctions.contains(data[0])){
addSpam(socket);
}
$emit.apply(socket, arguments);
};
});
function maxSpamCheck(socket){
if(spamData[socket.username].spamScore>=maxSpam && !socket.spamViolated){
socket.spamViolated = true;
socket.disconnect();
}
}
function checkSpam(){
for(user in spamData){
if(spamData[user].spamScore>=1) spamData[user].spamScore-=antiSpamRemove;
}
return;
}
setInterval(checkSpam,antiSpam);
function addSpam(socket){
if(socket.spamViolated) return;
spamData[socket.username].spamScore+=1;
maxSpamCheck(socket);
}
// Then add this where your user is authenticated
function authenticate(socket){
socket.username = username // here you define username
socket.spamViolated = false;
spamData[socket.username] = {
spamScore: 0
}
}
Array.prototype.contains = function(k) {
for(var p in this)
if(this[p] === k)
return true;
return false;
};
basically binds to all emits and checks if the emit name is contained in spamCheckFunctions if it is it will add a spam point, if a user exceeds a spam score amount (maxSpam); he will be disconnected. And for every milliseconds defined at antiSpam will minus the user spam score defined at antiSpamRemove
I'm sure there are cleaner solutions but this one worked out pretty good for me :)
Just make sure to verify/authenticate the users.
this is how I authenticate them (not using nodejs as a webserver, but had django):
io.configure(function(){
io.set('authorization', function(data, accept){
if(data.headers.cookie){
data.cookie = cookie_reader.parse(data.headers.cookie);
return accept(null, true);
}
return accept('error', false);
});
});
now you can access socket.handshake.cookie['sessionid'] (in my case this worked with django)
then match the socket.handshake.cookie['sessionid'] with a entry where your sessions are stored on the webserver
That's a difficult problem in general. Two things you could do:
1) Use self-invoking functions on the client side, i.e.
(function(w) {
// define your sockets here
var socket = ...;
})(window);
Obviously it is on the client side so this is not really secure. But it's not bad to have such wall.
2) On the server side keep track of the frequency of posting. For example if someone posts 5 times in a second, then you can assume that it is a spam and you could block that socket. It is especially effective if combined with authentication and complex registration (so people will have problem in creating new account).
Use an md5/sha key which behaves like a cookie.
Generate a key for a specific user and send it to the client and always check that incoming requests have the same key
It won't be completely secure since the hacker can always find your key in either the source code or localStorage but try to hide it through obfuscation of your code
A way to prevent spam is to either implement user authentication and/or a packet rate limiter. Add a middleware function which keeps track of the socketId and the amount of packets being sent through that socket. When it exceeds your limit disconnect the socket.
You can even add an extra function which keeps track of the IP address of that socket, if an IP address will be disconnected too often due spam you can ban that ip. Add a check on your connection event which IP addresses are allowed.
Use rate-limiter-flexible package for limiting number of events per second. Limiting by IP is the most simple, but would be better to limit by userId if possible.
const app = require('http').createServer();
const io = require('socket.io')(app);
const { RateLimiterMemory } = require('rate-limiter-flexible');
app.listen(3000);
const rateLimiter = new RateLimiterMemory(
{
points: 5, // 5 points
duration: 1, // per second
});
io.on('connection', (socket) => {
socket.on('bcast', async (data) => {
try {
await rateLimiter.consume(socket.handshake.address); // consume 1 point per event from IP
socket.emit('news', { 'data': data });
socket.broadcast.emit('news', { 'data': data });
} catch(rejRes) {
// no available points to consume
// emit error or warning message
socket.emit('blocked', { 'retry-ms': rejRes.msBeforeNext });
}
});
});
Read more in official docs

socket.io - broadcast to certain users

I need to build twosome chat, using websockets (socket.io + node.js).
So, the simple example to broadcast message to all users:
socket.on('user message', function (msg) {
socket.broadcast.emit('user message', socket.nickname, msg);
});
But how can I broadcast it from certain user to certain user?
There are two possibilites :
1) Each socket has its own unique ID stored in socket.id. If you know the ID of both users, then you can simply use
io.sockets[id].emit(...)
2) Define your own ID (for example user's name) and use
socket.join('priv/John');
in connection handler. Now whenever you want send message only to John, you simply do
socket.broadcast.to('priv/John').emit(...)
Side note: the first solution provided cannot be scaled to multiple machines, so I advice using the second one.
You can use the socket.join(...) function to provide groups:
socket.on('init', function(user) {
if (user.type == 'dog') {
socket.join('dogs');
}
else {
socket.join('cats');
}
});
...
io.to('dogs').emit('food', 'bone'); // Only dogs will receive it
io.to('cats').emit('food', 'milk'); // Only cats will receive it

Categories