Socket io. Disable automatical joining a room identified by socket's id - node.js

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 });
};

Related

Move another user to a room with Socket.io

I am using Socket.io with Node.js and I would like to pair users up and send them to a new room.
When a user connects, within io.on('connection') I determine their compatibility with a user waiting to be paired in an array.
If the user who has joined is compatible with a waiting user, I want to move them both to a new room.
This is my current approach. Note: [1029387,1983934,9243802] is an array of user IDs.
var pendingPlayers = {
"spelling": {
"level1":[1029387,1983934,9243802],
"level2":[]
}
}
io.on('connection', function(socket) {
// check compatibility
// move current player and other queued player to new room
});
The only idea I have is to socket.emit('room', 'new room name'); from server to client, for the queued player to send this ID back to the server and then use:
io.on('connection', function(socket) {
socket.on('room', function(room) {
socket.join(room);
});
});
However, sending the room name to the client, to then send it back to the server seems awfully far-fetched. I'm hoping there's an easier way.
Your suggestions are gratefully received.
As per your comment
If there are no players already in the pendingPlayers object for the
respective level, the user who is joining a game is added to the
pendingPlayers object.
Rather than moving to object, make a new room with only this player for that level. Now when new player come as per your comment
Then, the next player who requests to join the respective level will
be paired with the player ID that is .push()'ed from the array
corresponding that level.
make that next player join the above room.
Maybe you need some logic like in the battleship game. It is used temporary 'waiting room'. I found it here:
https://github.com/inf123/NodeBattleship/blob/master/server.js
io.on('connection', function (socket) {
//firstly add player to room until opponent aren't come
socket.join('waiting room');
joinWaitingPlayers();
});
function joinWaitingPlayers () {
var clients = [];
for (var id in io.sockets.adapter.rooms['waiting room']) {
clients.push(io.sockets.adapter.nsp.connected[id]);
}
if (clients.length >= 2) {
//if we have a couple, then start the game
var game = new Game();
// live "waiting room"
clients[0].leave('waiting room');
clients[1].leave('waiting room');
// and then join both to another room
clients[0].join('game' + game.id);
clients[1].join('game' + game.id);
}
}

node.js socket.io app - How to kick someone from chat room?

So I have chat app in which only two users can be in a room at a time.
Please note that clientInfo is a global variable and anytime a new user joins a room, an object variable is passed to the server with username and room name. Then this code is executed
socket.on('joinRoom', function(userDetails){
clientInfo[socket.id] = userDetails; //variable contains name and room joined.
socket.join(userDetails.room);
}
I added a feature where a user can type 'kick' as the chat message which will call this function:
function kickUser(socket){
socket.leave(clientInfo[socket.id].room);
delete clientInfo[socket.id];
}
the above code works fine but how can i change the function to kick the other user in the room? I tried the code below but I still ended up kicking myself out. I take it that socket.leave() doesn't care about the socket id but it just uses the room name and the person who called the function.
function kickUser(socket){
var info = clientInfo[socket.id];
if (typeof info == 'undefined'){
return;
}
Object.keys(clientInfo).forEach(function(socketId){
if(info.room == clientInfo[socketId].room) {
if (info.name != clientInfo[socketId].name){
socket.leave(clientInfo[socketId].room);
delete clientInfo[socketId];
}
}
});
}
Try implement next idea
In socket.on('connection', function(socket){ ... }); you must identify user name and room for this connection/socket: client can send username/room on connection itself
socket.on('connect', function {
socket.send(JSON.stringify{do: "introduce", name: "User1", room: "dasRoom"})
})
or server can read it in client cookie. Also you can check here user count in room. This info can be stored in global vars.
User can do kick by send specified message by socket, e.g. {do: "kick
, user: "User1"}, or request url. e.g. /kick/User1.
Function Kick must implement logic to find user by name, send goodbay message to victim and disconnect socket. Also server must send User1 leave room to alive user by socket.

Random chat with two users at a time (Socket.io)

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

Rooms are not removed after everyone leaves

As far i read from the doc
that Rooms are left automatically upon disconnection and they are automatically removed when everyone leaves. But this is not the case of my actual code:
io.on('connection', function(socket) {
socket.join(MainRoom);
io.sockets.adapter.rooms[socket.id].owner = socket.username;
//send the list of available rooms on connection
socket.to(MainRoom).emit('updateList',io.sockets.adapter.rooms);
socket.on('getUpdateList',function() {
io.to(MainRoom).emit('updateList',io.sockets.adapter.rooms);
});
socket.on('msg', function(msg) {
io.to(MainRoom).emit('msgFront',msg);
});
socket.on('disconnect', function() {
console.log('leaving '+socket.id);
io.to(MainRoom).emit('updateList',io.sockets.adapter.rooms);
});
});
Notice that I'm using a MainRoom where all client are forced to join it just to make sure that everyone can talk to each other.
By default 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.
My problem is that after closing/refreshing the browser tab, all previously joined rooms are still there, and the number of rooms is incremented(on connection the socket join new rooms automatically..)
Anyone can explain this behavior ?
Solved:
The problem is that i extended the rooms object bu adding owner attribute :
io.sockets.adapter.rooms[socket.id].owner = socket.username;
So that the extended room can't be removed. the solution that i found is to store owner attribute outside within an associative array, that's it.

socket.io private message

I've beeen scouring the Net with no luck. I'm trying to figure out how to send a private message from one user to another. There are lots of snippets, but I'm not sure about the client/server interaction. If I have the ID of the socket I want to send to, how do I send it to the server, and how do I ensure the server only sends the message to that one receiver socket?
Is there a tutorial or walkthrough that anyone knows of?
No tutorial needed. The Socket.IO FAQ is pretty straightforward on this one:
socket.emit('news', { hello: 'world' });
EDIT: Folks are linking to this question when asking about how to get that socket object later. There is no need to. When a new client connects, save a reference to that socket on whatever object you're keeping your user information on. My comment from below:
In the top of your script somewhere, setup an object to hold your users' information.
var connectedUsers = {};
In your .on('connection') function, add that socket to your new object. connectedUsers[USER_NAME_HERE] = socket; Then you can easily retrieve it later. connectedUsers[USER_NAME_HERE].emit('something', 'something');
Here's a code snippet that should help:
Client-side (sending message)
socket.emit("private", { msg: chatMsg.val(), to: selected.text() });
where to refers to the id to send a private message to and msg is the content.
Client-side (receiving message)
socket.on("private", function(data) {
chatLog.append('<li class="private"><em><strong>'+ data.from +' -> '+ data.to +'</strong>: '+ data.msg +'</em></li>');
});
where chatLog is a div displaying the chat messages.
Server-side
client.on("private", function(data) {
io.sockets.sockets[data.to].emit("private", { from: client.id, to: data.to, msg: data.msg });
client.emit("private", { from: client.id, to: data.to, msg: data.msg });
});
Although we have nice answers here. However, I couldn't grasp the whole client server unique user identification pretty fast, so I'm posting this simple steps in order to help whoever is struggling as i did.....
At the client side, Get the user's ID, in my case I'm getting the username...
Client side user registration
//Connect socket.io
var systemUrl = 'http://localhost:4000';
var socket = io.connect(systemUrl);
//Collect User identity from the client side
var username = prompt('Enter your Username');
socket.emit('register',username);
The Listen to register on the server side to register user's socket to connected socket
Serve side code User registration
/*Craete an empty object to collect connected users*/
var connectedUsers = {};
io.on('connection',function(socket){
/*Register connected user*/
socket.on('register',function(username){
socket.username = username;
connectedUsers[username] = socket;
});
});
Send Message from the client side
$(document).on('click','.username',function(){
var username = $(this).text(),
message = prompt("type your message");
socket.emit('private_chat',{
to : username,
message : message
});
});
Receive message on server and emit it to private user
/*Private chat*/
socket.on('private_chat',function(data){
const to = data.to,
message = data.message;
if(connectedUsers.hasOwnProperty(to)){
connectedUsers[to].emit('private_chat',{
//The sender's username
username : socket.username,
//Message sent to receiver
message : message
});
}
});
Receive message on client and display it
/*Received private messages*/
socket.on('private_chat',function(data){
var username = data.username;
var message = data.message;
alert(username+': '+message);
});
This is not the best, however you can start from here....
The easiest way I can think of is to have an hash of all the users on using their id or name as the key and have their socket as part of the value then when you want to send a message to just them you pull that socket and emit on it... something like this:
users[toUser].emit('msg',"Hello, "+toUser+"!");
if you have a web site that has register users with uid then you can create a room for each user and name the room by uid of the user.
first connect client to the server using :
var socket = io('server_url');
on the server side create an event for detecting client connection:
io.on('connection', function (socket) {}
then you can emit to client inside it using socket.emit(); and ask uid of current user.
on the client side create an event for this request and then send uid of the user to server.
now on server side join the connected socket to room using :
socket.join(uid);
console.log('user ' + socket.user + ' connected \n');
now you can send private message to a user using following line:
io.to(uid).emit();
if you use the code above, it doesn't matter how many tab user has already open from your web site . each tab will connect to the same room.
in socket.io 1.0 use
io.sockets.connected[<socketid>]
you can store just socket id. like so:
var users = {};
users[USER_NAME] = socket.id;
then:
io.sockets.connected[users[USER_NAME]]
.emit('private', {
msg:'private message for user with name '+ USER_NAME
});
You can create an unique room for messaging between USER_A and USER_B and both users must join to this room. You may use an UUID as a ROOM_ID (the 'socketId' in the following example).
io.on('connection', socket => {
socket.on('message', message => {
socket.to(message.socketId).emit('message', message);
});
socket.on('join', socketId => {
socket.join(socketId);
});
});
See Joining Rooms and Emit Cheatsheet
The way I got it to work is by introducing one more event "registered user". This basically triggers on the client side as soon as there is a registered user and then emits this even and passes "currentUser._id"
Client Side:
var socket = io();
<% if (currentUser) { %>
socket.emit('registered user', "<%= currentUser._id %>");
<% } %>
Server Side: Once the "registered user" even triggers, it joins the current user socket to the new room with the name which is that user id. So basically each registered user will be in a room which is his/her uid.
socket.on('registered user', function (uid) {
socket.join(uid)
});
Server Side: One there is a message sent, I pass the id of the user the message is sent to in the msg object and then emit to that specific room.
// Private chat
socket.on('chat message', function (msg) {
const uidTo = msg.uidTo
socket.in(uidTo).emit('chat message', msg )
}); `
});

Resources