I am working on socket for chatting. Here is my socket code in nodejs file which is working well.
The outer socket io.emit working good and emits the message to all the users which are connected to that conversationId.
But the socket.broadcast.emit(when user uses the app) which I am using to notify user, emits(socket.broadcast.emit) events multiple times. Why this is happening? Am I completely missing the socket approach.
socket.on('sendMessage', async(action2) => {
try {
action2.author = socket.decoded.id
action2.readBy = [socket.decoded.id]
action2.deliveredTo = [socket.decoded.id]
const createMessage = await Message.create(action2)
const sender = await User.findOne({ _id: socket.decoded.id }, { firstName: 1 })
const memebers = //some api call to get members
const promises = members.map(async(member) => {
// socket for message
const socketNotification = {
// my object
}
console.log(socketNotification, 'socketNotifcication')
socket.broadcast.emit(`messageSocket${member.memberId}`, socketNotification)
})
await Promise.all(promises)
io.emit(action2.conversationId, messages) // "newMessage"
} catch (err) {
throw err
}
})
From the Broadcast docs:
Sets a modifier for a subsequent event emission that the event data
will only be broadcast to every sockets but the sender.
https://socket.io/docs/server-api/#Flag-%E2%80%98broadcast%E2%80%99
So in your loop you are saying send this everyone but the original socket, and you call that multiple times. What you want to use it it.to
io.to(membersSocketID).emit('eventName', socketNotification)
It's unclear from your example if the messageSocket${member.memberId} is supposed to be the event name of if that is your specified socket.id.
This is a great cheatsheet for this https://socket.io/docs/emit-cheatsheet/
Side note, if your api call to get the member id's is significant you might be better off using rooms/namespaces and doing that query on connect to determine rooms.
use io.to(messageSocket${member.memberId}).emit() instead of socket.broadcast.emit()
Related
i am building a mobile app with ionic, i am using socket.io, i want to be able to send message to a particular user with an id, and not broadcast the message to everyone, the chat application is not a chat room style kind of app but a one on one chatting app like watsapp, i searched online but what i saw was not working, here is the code for the server side
const io = require('socket.io')(server);
io.on('connection', socket => {
console.log('New user connected');
socket.on('get_all_msg', data => {
DBConn.query(`SELECT * FROM chats WHERE rec_id = ? || send_id=?`, [data.id, data.id], (error, results, fields) => {
if (error) throw error;
io.to(data.id).emit('all_msg', results)
});
})
})
the id of the user i am chatting with is the data.id, i tried using io.to(data.id).emit('all_msg', results) but the user did not receive any message, pls what am i doing that is not right
Here's the client side code
this.socket.emit('get_all_msg', {id:this.contactInfo.id})
this.service.socket.fromEvent('all_msg').subscribe((data:any) => {
console.log(data)
})
I am using ngx-socket.io in my ionic add
we need to map socket id to user id;
we can solve this using redis but simple way i did was
actually socket io itself joins the current into a room with its id(socketio) itself;
i was like, "why not join that socket into room with user_id"
backend side:
io.on('connection',socket=>{
socket.on('join-me',userid=>{
socket.join(userid);
})
})
front end side:
const socket=io(`blahblah`);
socket.on('connect',()=>{
socket.emit('join-me',current_user_id);
})
when one user emits new-message event
we can get the participent id's and can simply loop over their ids and emit an event
socket.on('message',data=>{
//do some logic let say
participents=data.participents;
parsedMessage=someLogic(data);
for(id of participents){
//here is the magic happens io.to(room_id) here room id is user id itself.
io.to(id).emit('new-message',parsedMessage);
}
})
works with one to one and group chat!
I'm learning about node.js, express, and socket.io
I've managed to successfully set up my server and connect to it from the browser using localhost:3000
I can send information from client to server and vice versa no problems.
What I now need to do is this.
Let's say we have 2 clients connected to the server.
Each client will send a piece of information.
socket.emit('mydata', datavariable);
On server:
socket.on('mydata', function(datavariable) {
myArray.push(datavariable); //myArray declared globally
});
How do I code it so the server will do something like this.
if(myArray.length = #ofconnectedclients) {
socket.emit("filledUpArray", myArray);
}
Or another way of saying it I guess is, once you have gotten a response from all clients connected, then do something.
I want to receive a piece of information from all clients, and after I have received that info from all clients, then send that data (now stored in an array) to all my clients.
Thanks!
So, it sounds like your problem statement is this:
When all connected clients have sent a certain piece of information to the server, then I want the server to do something.
First thing you have to do is keep track of all connected clients. You can either use socket.io's internal data structures for that or you can create your own. Here's creating your own on the server:
// map of all currently connected client objects
// socket is the key, custom object is the value
// methods are for managing data in the Map
let connectedSockets = new SocketMap();
// socket connects
io.on('connection', function(socket) {
let socketData = {};
socketData.socket = socket;
socketData.dataReceived = false;
// add it to our Map
connectedSockets.set(socket, socketData);
// monitor disconnects too
socket.on('disconnect', function() {
// remove it from the map
connectedSockets.delete(socket);
});
});
The second thing you need to do is to keep track of some per-socket state so you can initailize it at some point and then each time you receive a specific message from a client, you can check to see if all clients have sent the desired message. We'll create a couple functions to do that:
// this actually physically goes above the previous code, but I'm just placing it
// here for explanation purposes
class SocketMap extends Map {
// clear the dataReceived Boolean to false for all sockets
initReceived() {
for (let [key, value] of this) {
value.dataReceived = false;
}
}
// return Boolean if all sockets have dataReceived set to true
haveAllReceived() {
for (let [key, value] of this) {
if (!value.dataReceived) {
return false;
}
}
return true;
}
setReceived(socket) {
let data = this.get(socket);
if (!data) {
return false;
}
data.dataReceived = true;
}
}
Now you have methods on the connectedSockets Map object for keeping track of a dataReceived Boolean for each socket and for initializing them all to false and for setting one to true when a particular condition occurs (receiving a message) and for checking if they have all set the flag. So, you just have to determine when, in your code you want to call those messages.
Let's suppose that when the message you're waiting for from each socket is someMsg. So, you create a listener for that message and process it. We can incorporate that code into our previous connection handling code here:
// map of all currently connected client objects
// socket is the key, custom object is the value
// methods are for managing data in the Map
let connectedSockets = new SocketMap();
// socket connects
io.on('connection', function(socket) {
let socketData = {};
socketData.socket = socket;
socketData.dataReceived = false;
// add it to our Map
connectedSockets.set(socket, socketData);
// monitor disconnects too
socket.on('disconnect', function() {
// remove it from the map
connectedSockets.delete(socket);
});
// process this message
socket.on('someMsg', function(data) {
connectedSockets.setReceived(socket);
// if all sockets have now sent their data, then do something
if (connectedSockets.haveAllReceived()) {
// do your thing here
// reset state, ready for next time
connectedSockets.initReceived();
}
});
});
Use case:
I have to handle several events which require an "available client". So in each event handler I first have to try to get an available client. If there is no client available I'll respond with a "Service unavailable" message. Right now I've implemented that requirement like this:
public constructor(consumer: RpcConsumer) {
consumer.on('requestA', this.onRequestA);
}
private onRequestA = async (msg: RpcConsumerMessage) {
const client: RequestClient = this.getClient(msg);
if (client == null) {
return;
}
msg.reply(await client.getResponseA());
}
private getClient(msg: RpcConsumerMessage): RequestClient {
const client: RequestClient= this.clientManager.getClient();
if (client == null) {
const err: Error = new Error('Currently there is no client available to process this request');
msg.reply(undefined, MessageStatus.ServiceUnavailable, err);
return;
}
return client;
}
The problem:
I don't want to check for an available client in all event handlers again and again. Instead I thought a middleware would perfectly fit into this use case. It would check for an available client and passes on the client instance if there is one. If there is not available client it will respond with the error message.
The question:
How would I write such a middleware for this case?
Build a curried method for this:
private withClient(cb: (client: RequestClient) => string | Promise<string>) {
return function(msg: RpcConsumerMessage) {
const client: RequestClient= this.clientManager.getClient();
if (client == null) {
const err: Error = new Error('Currently there is no client available to process this request');
msg.reply(undefined, MessageStatus.ServiceUnavailable, err);
return;
}
msq.reply(await cb(client));
};
}
So you can use it as:
private onRequestA = withClient(client => client.getResponseA());
If I understand correctly I don't think you actually NEED middleware, although you might choose to go that route.
You can just have a module that is in charge of finding a client and serving one up if it is available. This would look something like this:
const _client;
module.exports = {
getClient
}
getClient(){
return _client;
}
function setClient(){
//Your logic to find an available client
//run this whenever a client disconnects (if there is an event for that) or until you are connected to a client
_clent = client; //When you find that client set it to `_client`. You will return this everytime someone calls getClient.
}
The advantage here is that once you find a client, the module will serve up that same client until you are disconnected from it. The trick then is just making sure that you are always trying to connect to client when you are disconnected - even when there are no requests. I hope this makes sense.
I'm having trouble displaying the correct amount of online users. A question similar to this have shown me that I can count the users this way:
var count = 0
socket.on('connection', function(client) {
count++;
client.broadcast({count:count})
client.on('disconnect', function(){
count--;
})
})
The issue I'm constantly running into with that method is that when a user happens to reload the page too quickly, the counter pulls in too much it can throw out.
As you can see, on the right side of the image, a user spammed the reload and it caught more users online than there actually is. (There was only one user on the server at this time)
My question is is there a better or more reliable way to count the exact amount users online without the extra 'virtual users', without using the users++/users-- method?
If they're logging in as a user, then you should authenticate them to the socket. Use that authentication to see if they already have a session, and disconnect them decrementing the count, before you increment it again with the new session.
An example below. The clients objects stores the connected clients, with values being the sockets they're connected to.
var clients = {};
socket.on('connection', function(client) {
//Authenticate the client (Using query string parameters, auth tokens, etc...), and return the userID if the user.
var userId = authenticate(client);
if ( !userId ) {
//Bad authentication, disconnect them
client.disconnect();
return;
}
if (clients[userId]) {
//They already have a session, disconnect
clients[userId].disconnect();
}
//Set session here
clients[userId] = client;
client.broadcast({count: Object.keys(clients).length})
client.on('disconnect', function(){
delete clients[userId];
})
})
Could do this pretty cleanly with the Observable pattern (using RxJS v5 here):
const { Observable } = require('rxjs')
const connections = Observable.fromEvent(socket, 'connection').mapTo(1)
const disconnections = Observable.fromEvent(socket, 'disconnect').mapTo(-1)
// emit 1 for connection, -1 for disconnection
Observable.merge(connections, disconnections)
.scan((total, change) => total + change, 0) // emit total
.subscribe(count => client.broadcast({ count }))
You can use Server Sent Events for this purpose really.
Take a look at https://javascript.info/server-sent-events
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