I am writing a chatting app. Right now, when a new conversation between user A and user b is initiated by user A, user A sendS a socket message to server with user B's userId.
The Server checks whether there's a conversation existing between the two users, if not, creates one, and have user A join the new conversation(clientA.join(newConversationId)). But I don't know how to have user B join the room too if user B actually has a connected socket now.
What I think might work is keeping an object mapping userId to socket.id, so I can get user B's socket id by B's userId sent along with A's origin message. And then I'll get B's socket by its socket ID, and have it join the conversation too.
The problem is, I don't know how to get a socket by a socket ID. I don't think there's an official document of this. Or is there other better way to deal with something like this?
Yes, you have to keep track of your users id.
This code may help you a little with that.
var io = require("socket.io").listen(conf.port);
var customIds = [];
io.on("connection", function (socket) {
socket.on("login" function (data) {
customIds[socket.id] = data.userId;
});
/**
* Executes when a client disconnect.
* It deletes this client and updates and emits the client new client list
*/
socket.on("disconnect", function () {
// leave the current room
//socket.leave(socket.room);
// emit event
//socket.broadcast.to(socket.room).emit("clientDisconnected",customIds[socket.id]));
// delete the custom id from the custom id array.
customIds.splice(socket.id, 1);
});
}
You can also save your userid like this (Do not modify socket.id)
socket.userId=XXXX
Get a list of clients and look for the user id you need
io.sockets.clients();
Related
So I am creating a chat application and I want to handle multiple chat rooms. Now I watched some tutorials and came up with a way.
const io = require("socket.io")(http);
io.on("connection", (socket) => {
socket.on("joinRoom", (roomid) => {
//Joining the room
socket.join(roomid)
//Broadcasting all previous messages
io.to(roomid).emit("messages",allPreviousMessages)
})
socket.on("chatMessage", (data) => {
//Saving msg to dB then broadcasting
io.to(roomid).emit("message",receivedMessage)
})
socket.on("disconnect",(data) => {
//updating user's lastSeen info in dB
})
})
So on my frontend when user clicks on a chatroom we call the "joinRoom" event and connect to the room and on clicking another chatroom make the same process of joining room.
Is this an ideal way for handling multiple chatrooms? If not so please let me know a better solution.
I think the best way to implement private rooms or channels or chats is this way. I have implemented an example for these three sections. Link
User token and api must be authenticated before connecting to socket.io. If this part is ok it will connect to socket.io otherwise it can't cause you to see the event that is there. Something happens by calling each of them. For example, by calling this onNotificationForVoiceCall event, the received data is first checked, then it is checked whether this user is present in the list of online users or not, and the state of the next step is checked. Whether or not this room has already been created in the database, the response of all these operations is returned to the user by socket.emit,
And I fixed some bug in project.
If anyone is experienced with Websockets / Socket IO hopefully you can point me in the right direction.
I made a Discord Clone and I'm trying to optimize it to scale better. Right now when a user sends a message I query the DB for all users part of that server, and emit a message to their specific socket. This is obviously not going to scale well as every message requires a expensive query and lookup in the client list
// Emit messages to only users part of specific server
// Will only return list of users part of server and active in last 10 minutes
sqlQuery = `SELECT userservers.user_id FROM userservers
JOIN users ON users.user_id = userservers.user_id AND users.user_last_active > (NOW() - INTERVAL 10 minute)
WHERE server_id = ${sql.escape(serverId)}`;
const users = await sql.query(sqlQuery);
action = { type: "message", payload: msg };
// Iterate over users, and find them in clients list
// Emit over socket only to that user
users.forEach((user) => {
clients.forEach((client) => {
if (client.userId === user.user_id) {
io.to(client.id).emit(user.user_id, action);
}
})
});
However using Rooms for each Sever would eliminate my need to query the DB. I understand I can do this when the socket server first starts
// Get server list from Mysql DB
servers.forEach((server) => {
socket.join(server.name);
}
However my issue becomes, when a user create a new server once the application is already running It will not update the list.
I am probably missing some concept on creating dynamic rooms.
EDIT : I am thinking the solution could be that every time a "server" is created, I send a message to the socket server so it can join that "room"
Right now when a user sends a message I query the DB for all users part of that server
I think you can submit broad cast message to all online users, so instead of forEach client => io.to(clientId) you can submit broad cast message to all connected users io.emit('some event', { for: 'everyone' });
also I'm wondering why you are creating many servers? you can divide your server into namespaces by using const namespace = io.of('/thisIsASeparateNamespace'); and also you can submit broadcast messages to all users inside this name space by namespace.emit('some event', { for: 'everyone in name space' });
So your chat structure can be like this
Server
Namespaces // for separate chat app / or like slack work spaces
Rooms // for group chatting
ClientID // for one to one
I keep track the list of every users connected in the array.
So if there is a new connection, it will check whether the user is already on the list or not, if he was already on the list, then assign their socket.id with the corresponding socket.id on the list, otherwise just add them to the list.
It's for preventing same user counted as 2 user while he attempt to do multi-login.
Object.keys(client).forEach(function (key) {
if (client[key].id == data.id){
is_connected = true;
socket.id = key;
}
});
I have no problem handling the messages/chat that was sent/received by the user who attempt multi-login.
socket.on('chat', function(msg){
var data = {"name": client[socket.id].name, "message": msg};
io.emit('chat', data);
});
The io.emit for the chat message was succesfully sent to the user who attempting multi-login.
The problem I got was whenever the user decide to logout/disconnect from the server.
io.emit('user_leave', client[socket.id].id);
[Multi-Login Case] -> Multi-User and Dual-User are same user attempting Multi-Login
Whenever the Main-User disconnected from the server, the Dual-User received 'user_leave' sent by the server, because io.emit supposed to send it to all sockets.
But not otherwise, while the Sub-User disconnected from the server, the Main-user do not receive 'user_leave' emitted by the server.
*Note: Main-User is login first, then the Dual-User. So the Main-User information was saved directly in the array, while the Sub-User socket.id was assigned with the Main-User socket.id
[Update]
B2 socket.id was assigned with B1 socket.id, the io.emit for chat work perfectly while io.emit for disconnect only emitted to All except Dual-User(B2)
socket.id is used internally by socket.io for its own socket list. You cannot overwrite that or you break some of its ability to maintain its own data structures.
You have two choices:
You can use the existing socket.id value as is (without overwriting it) so you don't break existing behavior. It is already guaranteed to be unique on the server.
You can use a different property name for your own id such as socket.userId and then you won't conflict.
If you need to, you can maintain a map between your own custom id and the socket.io socket.id so you could get to one from the other.
Similar question here: Socket.io custom client ID
generateId prop of io.engine object can be used for to set the custom id.
Using this way, the all socket ids can be created on the server side without any issue.
Actually I wrote an answer for a similar question today.
An example:
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
io.engine.generateId = function (req) {
// generate a new custom id here
return 1
}
io.on('connection', function (socket) {
// listing the default namespace rooms
console.log("rooms: ", io.nsps["/"].adapter.rooms);
})
The console output would be as the following:
rooms: { '/#1': Room { sockets: { '/#1': true }, length: 1 } }
It seems to be it has been handled.
It must be in mind that socket id must be unpredictable and unique value with considering security and the app operations!
Extra: If socket.id is returned as undefined because of your intense processes on your generateId method, async/await combination can be used to overcome this issue on node.js version 7.6.0 and later. handshake method of node_modules/engine.io/lib/server.js file should be changed as following:
former:
Server.prototype.handshake = function (transportName, req) {
var id = this.generateId(req);
...
}
new:
Server.prototype.handshake = async function (transportName, req) {
var id = await this.generateId(req);
...
}
I am working on a realtime application at the moment, that allows a user to add other others to a "team", I am wanting to notify a user they have been added in realtime. However I can not for the life of me work how to implement this, I think it is the documentation that is confusing me more than anything,
The user must be subscribed to the channel that the event is being triggered on
How can this be possible? Surely by the fact that I am trying to add some one to a group in a realtime, and that group is a unique channel they cannot me subscribed too it?
Here is my attempt, and explanation of the process.
1) A user logins into an app and is subscribed to a unique to them channel "presence-user_{id}" where id is there user id from the database.
2) A user elsewhere wants to add a user to their group, a select that user which runs the following code,
App['presence-' + 'user_' + newUser.get('id')] = App.pusher.subscribe('presence-user_' + newUser.get('id'));
App['presence-' + 'user_' + newUser.get('id')].trigger('client-user-added', { project : self.model.get('id') });
newUser is a backbone model detailing the user details of the selected user. What I am trying to do here is subscribe the user who is trying to a user to a group to that users channel.
This should then fire this method listenter,
App['presence-' + 'user_' + App.Session.get('user_id')].bind('client-user-added', this.RT_addProject, this);
This in turn should fire the following the method on the added clients application,
RT_addProject: function(data) {
console.log(data);
},
however it does not. What is the correct method of triggering client events?
TLDR; I want notify a user in realtime when they are added to a group using pusher.
There key points to presence channels are:
The presence channel name should be shared between the users that you wish to join the group - the act of subscribing to the channel adds them to the channel.
Each user on a channel needs a unique ID that you supply via your server (probably the user session ID) via an auth callback. This uniquely identifies them on the channel.
If the authentication is successful they are subscribed. Upon subscription the client get an initial list of existing users on the channel via the pusher:subscription_succeeded event.
Existing subscribers to the channel are notified of the presence of new users via pusher:member_added events.
As users go offline (close the browser, navigate between pages or unsubscribe via code) all other users on the channel are informed that the user has left the channel via pusher:member_removed events.
For example. On the client:
var pusher = new Pusher(APP_KEY);
var chatChannel = pusher.subscribe('presence-chat');
chatChannel.bind('pusher:subscription_succeeded', function(members) {
members.each(memberAdded);
});
chatChannel.bind('pusher:member_added', memberAdded);
chatChannel.bind('pusher:member_removed', memberRemoved);
function memberAdded(member) {
// add to UI etc.
}
function memberRemoved(member) {
// remove from UI etc.
}
The act of subscribing results in the Pusher JS library making an auth POST request to a server endpoint.
Server pseudo code:
var channelName = authRequest.postParam['channel-name'];
var socketId = authRequest.postParam['socket-id'];
var pusher = new Pusher(YOUR_CREDENTIALS);
if( currentSessionUserAllowedToSubscribeToChannel(channelName) ) {
var uniqueUserId = getCurrentSessionUserUniqueId();
var customUserInfo = getCustomUserInfo();
var authSignature = pusher.presence_auth(channelName,
uniqueUserId,
customUserInfo);
send200AuthJSONToClient(authSignature);
}
else {
send401ToClient();
}
What is the correct method of triggering client events?
Client events can only be triggered on private or presence channels once the subscription has succeeded:
var pusher = new Pusher(APP_KEY);
var chatChannel = pusher.subscribe('presence-chat');
chatChannel.bind('pusher:subscription_succeeded', function() {
var wasTriggered = pusher.trigger('client-event', {some: 'data'});
console.log(wasTriggered);
});
TLDR; I want notify a user in realtime when they are added to a group using pusher.
If userA wants userB to join channelX then you need to signal userB about the existence of channelX so they can subscribe to it.
userB can only receive events on channels they are subscribed to. So, userA somehow needs to send an event on a channel userB knows about. A common solution to this is to have a per user channel e.g. userA-notifications and userA needs to trigger an event on this channel via the server. Ultimately you need an authority to stop unwanted events from being triggered.
This answer is relevant to the process described above: https://stackoverflow.com/a/28165893/39904
Is it possible to check whether a user has subscribed to a certain channel in socket.io?
Let´s say I have a channel called news. User subscribed to that channel on client-side. But the site´s data is dynamic, therefore the news-tab of the site might not be open at any time. I do not want to create the content for the news-tab if the news tab is not open on the client-side. I know that the news-tab is not open, when the user has not subscribed to the news channel.
Is there a way to check that?
You can use io.sockets.clients('news') and this will return the socket id's of all clients. If you know the client socket.id, you could also call io.sockets.manager.roomClients[socket.id] (room name will have '/' leading character)
This is a sample I use for my admin clients to call to get client counts per room:
socket.on('countClientsInRoom', function (room, callback) {
var count = io.sockets.clients(room).length;
callback(count);
});