Related
In docs it is said: "Each Socket in Socket.IO is identified by a random, unguessable, unique identifier Socket#id. For your convenience, each socket automatically joins a room identified by this id."
I am wondering if there is an option to disable such feature.
My solution was:
io.on('connection', function (socket) {
leaveDefRoom(socket);
[...]
}
function leaveDefRoom(socket){
var room = socket.adapter.rooms;
for (var key in room){
if (key.charAt(0) == '/') {
socket.leave(key);
return;
}
}
}
In socket.io. Every time you emit event. socket.io send the event to the client in this room. If you remove user from the room, you cannot send this user messages. Even broadcast will not work.
Anyway, if you really want, you can leave this room, like any other room:
You can change socket.js file and disable this option:
https://github.com/socketio/socket.io/blob/master/lib/socket.js#L289
Socket.prototype.onconnect = function(){
debug('socket connected - writing packet');
this.nsp.connected[this.id] = this;
// You have to remove this line below:
this.join(this.id);
this.packet({ type: parser.CONNECT });
};
My goal is to create a one to one chat based on two different users. The only way that I could think of is to use socket.io rooms
But the problem right now is that how do i create unique room?
For example
socket.on('join', function(room) {
socket.join(room);
});
Do i need to emit the room from the client, if so , how do I make it unique. Imagine there are thousands of users.
The chat application, is similar like facebook chat application. Where you can chat one on one.
Do i need redis or mongodb to store the room? Anyone of you who have experience using socket.io in scale, please do share your opinion
Thanks!
A room always will be unique, when you do socket.join('roomname') if the room not exist it will created and this socket will join it, if exist the socket just will join it.
So if you want that client A join in the room where is client B for example, from client A you can send a event like:
socket.emit('joinroom', roomname);
On sever:
socket.on('joinroom', function(data){
socket.join(data)
})
Anyway when a socket connect , it create and join his own room automatically , the name of this room will be the id of the socket, so i think is not neccessary create new rooms for a chat based on two different users.
Everything that you need is link the socket id with a permanent property of the user.
EDIT:
I leave you here a simple chat app example where you can open multiple conversations:
server.js: https://gist.github.com/pacmanmulet/b30d26b9e932316f54b2
index.html: https://gist.github.com/pacmanmulet/6481791089effb79f25f
You can test it here :https://chat-socket-io-example.herokuapp.com/
I did not use rooms, it have more sense when you want emit to a group of sockets, not to a only one.
Hope you can understand better my idea with that.
you need to store the room number somewhere(any database).You have to do this because you have to keep your server stateless.
Let us assume that you are creating a private chat only for two people.The room number has to be unique. so one approach is to use the user's email id and join them to create a new string and emit it back to the users.this is tricky because we don't know the order in which the strings are joined. so we join them by a string not used in normal email name(eg :'"#!#!#!!#!#!#!').we can split it on the server side and compare emit the results.
The actual message body will be
{
room:a#gmail.comb#gmail.com,
from:a,
message:'hi buddy how are you?'
}
CLIENT side code
const roomName = a#gmail.com+'#!#!2!#!#"+b#gmail.com
socket.emit('room', { room: roomName });
this.socket.on('joined', data => {
console.log('i have joined', data.room)
store the room name room: data.room })
})
socket.on('chat',data=>console.log(`received chat from ${data.from} from the message room ${data.room}`)
used '#!#!2#!#' just because we can separate them on the server side and check if the room already exists.
SERVER side code
const room =[]//this variable you have store in database and retrieve it when needed.
socket.on('room',data=>{
if(room.length!=0){
const temp = data.room.split('!#!#2#!#!').reverse().join('!#!#2#!#!');
if(room.includes(temp)){
socket.join(temp)
console.log('joined room',temp)
socket.emit('joined',{room:temp})
console.log(room);
} else if(room.includes(data.room)){
socket.join(data.room)
console.log('joined room', data.room)
socket.emit('joined', { room: data.room})
console.log(room);
}
}else{
socket.join(data.room);
room.push(data.room)
console.log('joined room',data.room);
socket.emit('joined', { room: data.room })
console.log(room);
}
})
I tried to do a minimal example of where you can only be in one room at a time (apart from your default socket.id room) and only other sockets in the same room as you will receive your messages. Also you can change rooms.
The basic premise is, if socket A is in room 'xyz' and so is socket B, on the server side you can do socket.to('xyz').emit('message', 'hello') for socket A, and socket B will receive the message, but another connected socket C which isn't in room 'xyz' won't.
You can create room at server runtime, I used both users id as room id, Ex : '100-200' for demo purpose. May be you can use some more complex approach.
I just started learning NodeJS and Socket.io. Until now I have this demo code, from official socket.io site:
http://socket.io/demos/chat/
I am able to get the unique client's ID of each user (socket) which connects, I am still trying to figure out, How can I make my code to only connect with 1 random user at a time when somebody runs the application. I just want to make random chat like Omegle (http://www.omegle.com/).
Only two users should randomly connect and chat with each other till they re-run the app, if they come back they should get connected with someone else who is in the online queue.
What changes do I need to do to have a similar behaviour?
Update
Added Client site code, main.js
$(function() {
var FADE_TIME = 150; // ms
var TYPING_TIMER_LENGTH = 400; // ms
var COLORS = [
'#e21400', '#91580f', '#f8a700', '#f78b00',
'#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
'#3b88eb', '#3824aa', '#a700ff', '#d300e7'
];
// Initialize variables
var $window = $(window);
var $usernameInput = $('.usernameInput'); // Input for username
var $messages = $('.messages'); // Messages area
var $inputMessage = $('.inputMessage'); // Input message input box
var $loginPage = $('.login.page'); // The login page
var $chatPage = $('.chat.page'); // The chatroom page
// Prompt for setting a username
var username;
var connected = false;
var typing = false;
var lastTypingTime;
var $currentInput = $usernameInput.focus();
//Own Global
var room = '';
var socket = io();
function addParticipantsMessage (data) {
var message = '';
if (data.numUsers === 1) {
// message += "there's 1 participant";
// Status Message
message += "Waiting to connect with someone";
} else {
// message += "there are " + data.numUsers + " participants";
//Status message update
message = "You are connected to a stranger! Say Hey!";
}
log(message);
}
// Sets the client's username
function setUsername () {
username = cleanInput($usernameInput.val().trim());
// If the username is valid
if (username) {
$loginPage.fadeOut();
$chatPage.show();
$loginPage.off('click');
$currentInput = $inputMessage.focus();
// Tell the server your username
socket.emit('add user', username);
// Own
socket.emit('login', {'username' : 'Faizan'});
}
}
Although I would close this question because it's too vague, I feel obliged to give you some insight since I worked way too much with websockets in the last years (although not that much with socketio & nodejs). I suppose some simple guide and relevant links could help you. So first,
Kind of relevant intro
You should already know that Socket.io is a WebSocket implementation.
WebSockets (WS) allow server to send data whenever it wants, as long as the connection is still open, as opposed to old way: client querying all the time asking, if there is an update on the server.
You can imagine a woman and a man at the end of a party: "Thanks for tonight, I'd love to repeat it sometimes soon. Would you give me your number?" - asks the old man. "Ughhh, you know what, better give me yours, I promise I will call you!"
If the girl were to give him her number, he'd call a few times a day asking if she'd go somewhere (and she'd reply no). The other way around, she would call him only if she wanted to go and he would go. Of course he would.
I got a bit carried away, but you get the picture. The woman is a server, the guy is a client.
What is important to understand
(Absolute basic, you should know this =>)
When client connect to your server, (s)he should be served a html page and some javascript, which establishes connection to your WS server. In the code you've posted, Express is used as http server. Check this example to see how you should give user html&js.
You'll also notice namespaces and rooms in most of these tutorials. These are used for separating users into subcategories. One server may contain multiple namespaces (by default only one) and each namespace may contain multiple rooms. You probably won't need to bother with namespaces, one is just enough for your case. You will, however, need to understand rooms (more on that later).
Next thing, taken from your code
io.on('connection', function (socket) {
It's important to know, that socket here basically represent one connected client (in one namespace, but possibly in multiple rooms). You can do all sort of stuff with it, most notably:
install event handlers on it (that's what you do when you call socket.on(event, handler(data))
send events to it with socket.emit(event, data)
send broadcast event to all users with socket.broadcast.emit(event, data)
add/remove it to/from room with socket.join(room), socket.leave(room) respectively.
work with it as with an ordinary variable - store it wherever you want and then reuse it
Do you see the definition of numUsers in your code? That's a global variable which is shared with all clients, since nodejs is single-threaded. In the example it is being incremented inside one of the event handlers. Think we could use something like that? YES.
We can define global variable, queue for example. Or Q if you want. Point is, it can be an array used to store sockets, or rather clients, which are not currently in chat room.
At the end of this section I'd like to point out another obvious thing.
io.on('connection', handler); defines an event handler for 'connection' event happening on the io object (WS server). This is triggered each time client makes connection to your WS server (in your case, through javascript ran inside client's browser). Argument to the method is socket and it is this method where you should add event listeners for each client (that you already do in the code, particularly handling events 'new message', 'add user', 'typing', 'stop typing' and 'disconnect').
What events shall you need
That really depends on how complex you want your app to be. In my opinion, the bare minimum would be (note that you can change the event names, but 'disconnect' should stay 'disconnect'):
event name -> data given
Events handled on server side
login -> username (how the user should be called), possibly password if you want to enable registration
message -> text (content of the message being sent)
leave room -> room name
disconnect
Event handled on client side
connect
chat start -> name (second client's name), room (so we can leave it)
chat end -> no data required if you want to allow only one chat at the same time. In case of multiple chats you should also include which chat got closed
disconnect
Last note before we get started
This is only a rough sketch. There are multiple different crossroads along the way and which path you take mostly depends on your idea of the app. If you want to have multiple chats opened at the same time, you'll need to do some modifications. The same goes if you want to have more than two people connected to the same chat. Here I'll describe the simplest case possible, one chat, to people, no registration. Possibly what you want, judging from your post. Could be wrong.
Workflow
User opens your page in their web browser. You serve them html and javascript. The javascript will start new connection to your websocket server. Also, handlers for desired events should be defined at this point.
When the connection is established, this will be happening:
ON SERVER SIDE
io.on('connection', handler) will be fired. Only appropriate handlers for new socket will be installed, not doing anything else at this point.
ON CLIENT SIDE
socket.on('connect', handler) will be fired. Client should at that point have username stored somewhere. If not, no problem. The connection will be alive for quite some time. You can just call socket.emit('login', {'username':name) any time you wish after you are connected (in the example below I set up variable connected, which defaults to false but will be set to true as soon as connection is established.)
After you send login event from client, server registers it and saves it somewhere. Possibilities are endless, in this case I'll create global dictionary which maps socket.id to username. After that, user socket should be either paired with another one or added to queue.
So, if the queue is empty, simply append socket to global variable (it doesn't have to be an array, since we will pair the first available sockets together, however you may want to implement some history of users so they won't get connected to the same person again). If the queue is not empty, we pull one socket out of the Q and add them to the same room. Room name can be random or whatever you want, I'll use (socket1.id+'#'+socket2.id (if you wanted to have more users in one chat, this would have to be changed).
After you add them both, you'll need to notify them that their chat has started and send them the other peer's name. You will emit event 'chat start'.
Clients will catch the event and open new window. After that, whenever user types something and sends it, client emits event 'message' with payload {'message': user_inserted_text}. Server will capture it in the .on('message' handler and broadcast it to the room. Note:
Broadcasting means sending a message to everyone else except for the socket that starts it.
Note: I am really confused about socketio code right now. Look at this and tell me, if socket.rooms is an array or an object (socket.rooms[room] = room; ?? why?)
To avoid dealing with this not-straightforward code, lets create another global object, rooms, which will store the room names for us. We will map socket.id -> roomName there.
So when message comes, we can get name of the room by calling rooms[socket.id]. Then we broadcast the message like this:
socket.broadcast.to(room).emit('message', data);
Where data is what we received from the sender, therefore object {'text': 'some nice message'}. Your peer will then receive it (you won't) and display it (you should display it when you are sending it).
So the chat continues like this for a while, then one of the users decides (s)he wants to leave / chat with somebody else. They will close window and client will emit event 'leave room'. Server will capture it and send to the other party that her/his peer has disconnected. The same should happen if the client disconnects. After everything is closed, add both users to queue (or only one, if the other has disconnected from the server). In my code I will not make sure they won't get paired again. That is for the OP to code (can't be hard).
So, if you read this far, you deserve some actual code. Although I say actual, it's actually untested. But you know, it should work like this.
Some code
Client side
var connected = false;
var username = 'Faizan';
var room = '';
var socket = io('http://localhost');
socket.on('connect', function (data) { // we are connected, should send our name
connected = true;
if (username) socket.emit('login', {'username' : username});
});
socket.on('chat start', function(data) {
room = data.room;
show_chat_window(data.name); // some method which will show chat window
});
socket.on('chat end', function(data) {
hide_chat_window(); // this will close chat window and alert user that the peer ended chat
socket.leave(room); // it's possible to leave from both server and client, hoever it is better to be done by the client in this case
room = '';
});
socket.on('disconnect', function(data) { // handle server/connection falling
console.log('Connection fell or your browser is closing.');
});
var send_message = function(text) { // method, which you will call when user hits enter in input field
if (connected) socket.emit('message', {'text': text});
};
var leave_chat = function() { // call this when user want to end current chat
if (connected) {
socket.emit('leave room');
socket.leave(room);
room = '';
}
};
Server side
Not including initial requires and html/js serving., only global definitions and main io handler.
var queue = []; // list of sockets waiting for peers
var rooms = {}; // map socket.id => room
var names = {}; // map socket.id => name
var allUsers = {}; // map socket.id => socket
var findPeerForLoneSocket = function(socket) {
// this is place for possibly some extensive logic
// which can involve preventing two people pairing multiple times
if (queue) {
// somebody is in queue, pair them!
var peer = queue.pop();
var room = socket.id + '#' + peer.id;
// join them both
peer.join(room);
socket.join(room);
// register rooms to their names
rooms[peer.id] = room;
rooms[socket.id] = room;
// exchange names between the two of them and start the chat
peer.emit('chat start', {'name': names[socket.id], 'room':room});
socket.emit('chat start', {'name': names[peer.id], 'room':room});
} else {
// queue is empty, add our lone socket
queue.push(socket);
}
}
io.on('connection', function (socket) {
console.log('User '+socket.id + ' connected');
socket.on('login', function (data) {
names[socket.id] = data.username;
allUsers[socket.id] = socket;
// now check if sb is in queue
findPeerForLoneSocket(socket);
});
socket.on('message', function (data) {
var room = rooms[socket.id];
socket.broadcast.to(room).emit('message', data);
});
socket.on('leave room', function () {
var room = rooms[socket.id];
socket.broadcast.to(room).emit('chat end');
var peerID = room.split('#');
peerID = peerID[0] === socket.id ? peerID[1] : peerID[0];
// add both current and peer to the queue
findPeerForLoneSocket(allUsers[peerID]);
findPeerForLoneSocket(socket);
});
socket.on('disconnect', function () {
var room = rooms[socket.id];
socket.broadcast.to(room).emit('chat end');
var peerID = room.split('#');
peerID = peerID[0] === socket.id ? peerID[1] : peerID[0];
// current socket left, add the other one to the queue
findPeerForLoneSocket(allUsers[peerID]);
});
});
P.S.
The code above got a bit messy in the end. It can be done better and I encourage you to do better job than I did. Having this material at hand, go through it step by step and try to understand. I think I commented most, if not all of it. Good luck.
Tl;dr
I am not even surprised. Here, read a comic
I'm using Node.js with socket.io for a multiplayer card game, and there are game rooms which players can join.
For joining a room, I simply use:
io.sockets.on('connection', function (socket) {
socket.on('joinRoom', function (gid) {
//gid is game ID - create room name based on this and join the room
var room = 'game'+gid;
socket.join(room);
});
});
My question is, what is the quickest way to check if a socket is connected to a certain room? I know I could get all sockets in that room in an array and then check whether the target socket is in the array, but I'm guessing there should be a more basic syntax for this. What I'm looking for (in pseudo-code) would be
if(socket with ID "z8b7dbf98i" is in room "game10")
//do something
For the documentation, socket.io doesn't seem to have any simple way to do that. You really need to check if the client is in the room array, or the opposite: if the room is in the client array.
This can be done with an oneliner using indexOf:
if(socket.rooms.indexOf(room) >= 0)
Or the opposite search:
if(io.sockets.manager.rooms['/' + room].indexOf(socket.id) >= 0)
2021 response:
This was such a headache for me, but currently in version 4.0.2 of Socket IO, socket.rooms is a Javascript Set, so you can check if the given socket is in the room using .has():
if (socket.rooms.has('abc')) {
// Do something if socket is in room 'abc'
} else {
// Do something if socket is NOT in room 'abc'
}
If you need to check if the user is not in the room, you can simply use !:
if (!socket.rooms.has('abc')) {
// Do something if socket is NOT in room 'abc'
}
You can simply check like this
io.sockets.adapter.rooms['roomId']
This returns you a object with sId e.g.
{"1sEAEIZeuMgdhF35AAAA":true}
Updates specific to versions:
3.0+:
io.sockets.adapter.get('roomId')
1.4:
io.sockets.adapter.rooms['roomId']
1.3.x:
io.sockets.adapter.rooms['roomId'];
1.0.x to 1.2.x:
io.adapter.rooms['roomId'];
**Update:**
However one can check socket Id is in a given room or not with one-line as mentioned above only if server architecture has a single node server/single node process.
If you are using multi-node server, i.e. separate node process with load balanced.
Point to note here is, that the sockets are only registered on the process that they first connected to. So, you need to use socket.io-redis to connect all your nodes together to sync events, and what you can do to maintain list of socket Ids across multi-node is broadcast an event each time a client connects/disconnects, so that each node updates & maintains the real-time list of all the clients/socket Ids.
Background/Details:
The redis adapter extends the base adapter, but it only overrides/adds the following properties:
clients
broadcast
add
del
delAll
With the following code:
io.sockets.adapter.rooms["roomId"]; //Any of the above examples specific to versions mentioned above
you are querying the rooms property on socket.io-redis adapter. This wasn't overridden by the redis adapter, so you're actually querying the base adapter, which only knows about rooms/clients in the current process.
Why didn't the redis adapter override the rooms property? Might be because it would have to query the redis database instance to construct an object containing all rooms and connections on every access of this property. Not a good idea?
So as of this writing answer, you'll have to add that functionality to the adapter itself with a method like this:
/**
* One-Line code/property to check if the socket id is in given room.
*
* #param {String} room id
* #param {Function} callback (optional)
* #api public
*/
Redis.prototype.isSidExistsInRoom = function(room, fn){ ... }
where you will hit the redis database instance.
This should be part of the base adapter interface for all other adapters to implement. It's a common problem everyone will face one day, when they scale their servers ;)
P.S. Just a hint on another approach is to use the customRequest/customHook methods in socket.io-redis 3.1.0.
**Update with ver 5.2.0: (relevant multi node servers)**
Now redis adapter gives you rooms across processes/nodes as of 5.2.0
Source: [RedisAdapter#clients(rooms:Array, fn:Function)][5]
> Returns the list of client IDs connected to rooms across all nodes. See [Namespace#clients(fn:Function)][6]
io.of('/').adapter.clients((err, clients) => {
console.log(clients); // an array containing all connected socket ids
});
io.of('/').adapter.clients(['room1', 'room2'], (err, clients) => {
console.log(clients); // an array containing socket ids in 'room1' and/or 'room2'
});
// you can also use
io.in('room3').clients((err, clients) => {
console.log(clients); // an array containing socket ids in 'room3'
});
Happy Coding!
using "socket.io": "^2.3.0" this worked for me
if (!(io.sockets.adapter.rooms[room] && io.sockets.adapter.rooms[room].sockets[socket.id]))
// Join room or do any stuff
socket.join('product_' + product_id);
For current socket.io (1.0+ I suppose) structure of io object was changed, therefore you can now find out is there a user with given socketid in the room with given socket roomid by:
if(io.sockets.adapter.rooms[roomid][socketid])
This seems to have changed quite a lot with versions of socket.io, but as of this writing (version 1.7.2), this looks like it's stored in socket.rooms. It's an object that looks like this:
{
room_name: 'room_name',
second_room_name: 'second_room_name',
...
}
Before your socket has joined any rooms, as documented, you'll see that the socket is already in a room with it's own id, so socket.rooms will look something like:
{ PxtiIs22S7GhWPTSAAAA: 'PxtiIs22S7GhWPTSAAAA'}
That means you can check if a socket is in a room something like this:
io.on('connection', function(socket){
if(socket.rooms[myRoomName]){
// in the room
}else{
// not in the room
}
});
now socket.rooms looks like that:
{
"room1":"room1",
"room2":"room2",
...
"room100":"room100"
}
way to check if a socket is connected to a certain room:
if(socket.rooms[roomID]) return true;
answers from link
https://github.com/socketio/socket.io/issues/2890
If you still need it you can do next:
socket.room = "someRoom";
and then simply check it:
if (socket.room !== undefined){
socket.leave(socket.room);
}
"socket.io": "^4.4.1"
socket.rooms.has('roomName')worked for me.
return true if exist other wise false
Is it possible to get rooms which socket is currently in, without calling
io.sockets.clients(roomName)
for every room name and looking for this socket in results
In socket.io version 1+ the syntax is:
socket.rooms
Cross-compatible way
var rooms = Object.keys(io.sockets.adapter.sids[socket.id]);
// returns [socket.id, 'room-x'] or [socket.id, 'room-1', 'room-2', ..., 'room-x']
Update: Socket.io 3.0 Released
With 3.x Socket.rooms is Set now, which means that values in the rooms may only occur once.
Structure example: Set(4) {"<socket ID>", "room1", "room2", "room3"}
io.on("connect", (socket) => {
console.log(socket.rooms); // Set { <socket.id> }
socket.join("room1");
console.log(socket.rooms); // Set { <socket.id>, "room1" }
});
To check for specific room:
socket.rooms.has("room1"); // true
More about Set and available methods: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
Migration Docs: https://socket.io/docs/migrating-from-2-x-to-3-0/
From the Socket.IO Room doc:
io.sockets.manager.roomClients[socket.id]
When using a non-default adapter, such as socket.io-redis, socket.rooms doesn't seem to do the trick. The way I managed to get the rooms for a specific client without looping was to use io.sockets.adapter.sids[socket.id], which returns the rooms as an object.
{ 'R-ZRgSf7h4wfPatcAAAC': true, ROOM: true, ROOM_2: true }
Note that this doesn't list sockets on other processes, though!
socket.io v1.3.7, socket.io-redis 1.0.0
Version 1.7.3, socket.rooms contains socket.id, so remove it and get the list of rooms:
Object.keys(socket.rooms).filter(item => item!=socket.id);
In other version, you can print the socket and find the rooms.
Socket.io v2.1.1
So make sure you aren't accessing the sockets rooms in the disconnect event like I was, as they have already left the rooms by the time that event is triggered. If you want to do that try it in the disconnecting event - https://github.com/socketio/socket.io/pull/2332/files
Then you can use any of the following:
Object.keys(socket.adapter.rooms)
Object.keys(socket.adapter.sids)
Object.keys(socket.rooms)
Version 2.0.3
io.sockets.sockets[yourSocketID].rooms
That equal with
socket.rooms
Being sure that socket is in only one room at a time, my solution was:
var currentRoom = Object.keys(io.sockets.adapter.sids[socket.id]).filter(item => item!=socket.id);
socket.io 1.7.3 +
var currentRoom = socket.rooms[Object.keys(socket.rooms)[0]];//returns name of room
You can save room in socket itself when it joins a room
// join room
socket.join(room);
// update socket's rooms
if (socket.rooms) {
socket.rooms.push(room);
} else {
socket.rooms = [room];
}
Later you can retrieve all rooms that the socket is in by simply
socket.rooms
From the Server API documentation:
socket.rooms (object)
A hash of strings identifying the rooms this client is in, indexed by room name.
https://socket.io/docs/server-api/#socket-rooms
With socket.rooms you will get a set of socketId and its rooms.
So you can convert it into array and then slice it to get only the rooms like this:
[...socket.rooms].slice(1, );
And then we can iterate through that array or access any room, for example:
[...socket.rooms].slice(1, )[1] // to get first room