I want the server to send a message to all room clients when one of them disconnects.
Something like this:
socket.on('disconnect', function() {
server.sockets.in(room).emit('bye');
});
But...
How do I know which room to broadcast?
What if the client has joined to multiple rooms?
After inspecting the sockets object, I came up with this solution:
socket.on('disconnect', function() {
var rooms = io.sockets.manager.roomClients[socket.id];
for (var room in rooms) {
if (room.length > 0) { // if not the global room ''
room = room.substr(1); // remove leading '/'
console.log('user exits: '+room);
server.sockets.in(room).emit('bye');
}
}
});
not 100% on this - but give it a try:
when connecting to a room or adding a new user to the mix, remember their username or id or something in the socket:
socket.on('adduser', function(username){
socket.username = username;
socket.join('room');
}
Then listen to leave events on rooms:
socket.room.on('leave', function(){
socket.broadcast.to(this).emit(socket.username + ' says seeya!');
}
It's worth a try anyway - I'm sure something similar will work if this doesn't.
Related
I am creating a chess game website. I need to show online players available when a user clicks play online. From the online players list the user can click and send them req to play. Now my main problem is I'm new to socket.io and didn't find any resources regarding this online. I am not trying to show the users available in a single room rather the whole socket servers available players.
Here Online Players will be shown in a list
function sendName(name) {
var isNameValid = true;
for (var i = 0; i < users.length; i++) {
if (users[i].name === name) {
isNameValid = false;
console.log(name + " is already taken");
console.log(users);
socket.emit("nameError", "Name already exists, Try again");
return;
}
}
if (isNameValid) {
var room = generateRoomId();
users.push({ id: socket.id, name: name, room: room });
socket.join(room);
socket.emit("roomId", room);
}
}
All the users are pushed in the users' array on the server-side. but I don't know how to get their name and room info on the client-side then show it in the front end.
<div>
<h3 style="color:white;">Online Players</h3>
<ul id="users">
</ul>
</div>
Online players will be shown here.
Also when one user disconnects the online players' list should refresh which I think socket can do. I am doing a project and any help will be much appreciated and I will learn from it too.
I am adding an example code for server and client side. The full working code is available here. Each client will retrieve all the active rooms from the socket.io server after successful connections and join a room called "ChessRoom"
Server Side
const sockets = new Map() // holds all active sockets
io.on('connection', (socket) => {
console.log(`${socket.id}: connected`);
sockets.set(socket.id,socket) // add socket to Map object
socket.join("ChessRoom") // join socket to demo room
socket.on('disconnect', (data)=>{
console.log(`${socket.id} disconnected`);
sockets.delete(socket.id) // delete socket from Map object
})
socket.on('getrooms', (data,replyFn)=>{
console.log(`${socket.id}: received getrooms event with ${data}`);
const rooms = Array.from(io.sockets.adapter.rooms).map( (room) => {
return { name: room[0], members: Array.from(room[1])}
})
replyFn(rooms)
})
});
Client Side
socket.on('connect', async ()=>{
console.log(`${socket.id} connected`)
const data = "OptionalData"
socket.emit( "getrooms", data, (rooms) =>{
rooms.forEach((room, index) => {
console.log(`room${index}: `, room)
});
})
})
socket.on('error', async ()=>{
console.log(`${socket.id} error`)
})
In my project, I've a user, that user can in two channels , but i want this user can receive notifications for two channels different.
I can ask a question?
How to Client can join multiple room and receive 2 notifications the same time for different two rooms.
I have code here :
client.js
var socket = io.connect('localhost:3000');
socket.on('connect', function(){
socket.emit("subscribe",'test');
});
socket.on('message', function(message) {
console.log('Receive >>', message);
});
server.js
var client = redis.createClient();
io.sockets.on('connection', function (socket) {
var broadcast = function(channel, message){
socket.broadcast.to(channel).emit('message', message);
}
socket.on("subscribe",function(channel){
client.subscribe(channel);
socket.join(channel);
});
socket.on('disconnect', function() {
client.unsubscribe(channel);
client.removeListener(broadcast)
})
client.on("message", broadcast);
});
I wanna : socket.on("subscribe",function(channel){
client.subscribe(channel, channel1, channel2);
socket.join(channel, channel1, channel2);
});
But socket.io not support that.
Please give me an idea or a suggestion. Thanks so much!
I have started learning Node JS today and have made a chatroom.
When a user connects, it sends their user information to Node and that will store that in a var called user which is created in the anonymous function of io.on('connecting', function ... so I can have multiples of them for multiple users (I think that would work)
When the user disconnects, I remove an object of their user from usersOnline which is indexed by their user.id
I keep getting this error that tells me it is undefined:
Error TypeError: Cannot read property 'id' of undefined
at Socket.<anonymous> (/var/www/html/projects/hbc_chat/index.js:28:27)
The error is where I use delete usersOnline[user.id]
And here is the code:
// server user
var srvUser = {
id: 0,
display_name: 'HabboCreate',
fancy_display_name: 'HabboCreate',
picture: 'http://www.habbocreate.com/userdata/uploads/2f48a19f199bb5f018b3089fd4967902'
};
var usersOnline = {};
io.on('connection', function(socket) {
var user;
socket.on('connected', function(userObj) {
// store user
user = userObj;
// add to users online list
usersOnline[user.id] = user;
// send to clients
updateUsersOnline();
// send joined message
sendMessage(srvUser, user.display_name + ' joined the chat');
console.log(user.display_name + ' connected');
});
socket.on('disconnect', function() {
try {
// remove their user from the online list
delete usersOnline[user.id];
// send to client
updateUsersOnline();
// send left message
sendMessage(srvUser, user.display_name + ' left the chat');
}
catch (err) {
console.log('Error', err);
}
});
....
This does not seem to be a scope issue. Is it possible updateUsersOnline() changes the user info? I just ran this exact code, very similar to yours, without errors:
var usersOnline = {};
io.on('connection', function (socket) {
var user;
socket.on('connected', function (userObject) {
user = userObject;
console.log('user: ' + JSON.stringify(user));
usersOnline[user.id] = user;
console.log(JSON.stringify(usersOnline) + '\n');
});
socket.on('disconnect', function () {
try {
console.log('user: ' + JSON.stringify(user));
delete usersOnline[user.id];
console.log(JSON.stringify(usersOnline) + '\n');
} catch (err) {
console.log('err: ' + err);
}
});
});
I passed a user created like this in the client 'connected' event:
var userObject = {
id: new Date(),
display_name: 'some_name',
};
my_connection.emit('connected', userObject);
After the connected event I closed the connection. This is what the server logged:
user: {"id":"2016-12-17T04:49:05.123Z","display_name":"some_name"}
{"2016-12-17T04:49:05.123Z":{"id":"2016-12-17T04:49:05.123Z","display_name":"some_name"}}
user: {"id":"2016-12-17T04:49:05.123Z","display_name":"some_name"}
{}
No errors, so it seems something is removing the user object or at least its id property in between your connected and disconnect events.
right, well I've figured out what was wrong.
It was where I was on the chatroom page, I would restart the node server, my JavaScript wouldnt emit the user details (It only done so upon load), so when i went to refresh or whatever, disconnect event wouldn't be able to find info in the user variable and error
My fix was to make a request user event client side and then emit that on the server when there's a new connection, as to not rely on the page load sending the details
In the socket.io documentation I see an example of rooms
io.on('connection', function(socket){
socket.on('say to someone', function(id, msg){
socket.broadcast.to(id).emit('my message', msg);
});
});
I have a route /rooms/:roomId.
Is it possible to make the sockets being sent between the server and the client only hits the specific room?
I guess the server should be something like
io.on('connection', function(socket){
socket.on('new msg from client', function(roomId, msg){
io.to(id).emit('new msg from server', msg);
});
});
above and the client should send messages with
socket.emit('new msg from client', roomId, msg);
and get new messages simply with
socket.on('new msg from server', function () {
document.getElementById('msgs').appendChild(...);
});
But will this work? Shouldn't I join the room with socket.join(...) before I can do this?
For a haiku sharing application I made I have something like this:
io.on('connection', function(socket) {
var socket_id = socket.id;
var client_ip = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address.address;
clients.push(socket);
console.info('New client connected (id=' + socket.id + ').');
number_of_clients_connected++;
console.log('[' + client_ip + '] connected, ' + number_of_clients_connected + ' total users online.');
//when a socket is disconnected or closed, .on('disconnect') is fired
socket.on('disconnect', function() {
number_of_clients_connected--;
console.log('[' + client_ip + '] disconnected, ' + number_of_clients_connected + ' total users online.');
//on disconnect, remove from clients array
var index = clients.indexOf(socket);
if (index != -1) {
clients.splice(index, 1);
//console.info('Client gone (id=' + socket.id + ').');
}
});
So it keeps an array of clients and when certain messages need relaying you can specify the client socket ID...
//reads from latest_haikus_cache and sends them
socket.on('request_haiku_cache', function() {
latest_haikus_cache.forEach(function(a_latest_haiku) {
clients[clients.indexOf(socket)].emit('load_haiku_from_cache', a_latest_haiku);
});
});
The server is allowed to broadcast to any room so you can support letting a client ask the server to send to a room without that client being in the room. It really depends upon what you want to do.
So, if you want your server to have this code that lets any client send a message to any room of their choosing:
io.on('connection', function(socket){
socket.on('new msg from client', function(roomId, msg){
io.to(roomId).emit('new msg from server', msg);
});
});
Then, you can indeed do that and it will work. Whether it's appropriate or not is entirely up to your application and whether you want any client to be able broadcast to any room that it has the name of.
But will this work?
Yes, it will work.
Shouldn't I join the room with socket.join(...) before I can do this?
There is no need to have the client join the room unless it wants to receive messages for that room. You don't have be in the room in order to ask the server to send to that room if that's how you want to do it. So, all this is entirely up to your application and what is appropriate.
I have a route /rooms/:roomId.
Is it possible to make the sockets being sent between the server and
the client only hits the specific room?
I could not figure out what this part of your question means. If you care to explain further, I will try to help with this part too.
I'm trying to display a list of clients in a specific room. I just want to show their username, and not their socket id.
This where I'm at:
socket.set('nickname', "Earl");
socket.join('chatroom1');
console.log('User joined chat room 1);
var roster = io.sockets.clients('chatroom1');
for ( i in roster )
{
console.log('Username: ' + roster[i]);
}
Haven't had any luck getting it to list Socket IDs or anything. Would like it to return the nicknames however.
In socket.IO 3.x
New to version 3.x is that connected is renamed to sockets and is now an ES6 Map on namespaces. On rooms sockets is an ES6 Set of client ids.
//this is an ES6 Set of all client ids in the room
const clients = io.sockets.adapter.rooms.get('Room Name');
//to get the number of clients in this room
const numClients = clients ? clients.size : 0;
//to just emit the same event to all members of a room
io.to('Room Name').emit('new event', 'Updates');
for (const clientId of clients ) {
//this is the socket of each client in the room.
const clientSocket = io.sockets.sockets.get(clientId);
//you can do whatever you need with this
clientSocket.leave('Other Room')
}
In socket.IO 1.x through 2.x
Please refer the following answer:
Get list of all clients in specific room. Replicated below with some modifications:
const clients = io.sockets.adapter.rooms['Room Name'].sockets;
//to get the number of clients in this room
const numClients = clients ? Object.keys(clients).length : 0;
//to just emit the same event to all members of a room
io.to('Room Name').emit('new event', 'Updates');
for (const clientId in clients ) {
//this is the socket of each client in the room.
const clientSocket = io.sockets.connected[clientId];
//you can do whatever you need with this
clientSocket.leave('Other Room')
}
Instead of going deep in socket/io object , You can use simple and standard way :
io.in(room_name).clients((err , clients) => {
// clients will be array of socket ids , currently available in given room
});
For more detail DO READ
Just a few things.
when you have the socket you can then set the properties like: socket.nickname = 'Earl'; later to use the save property for example in a console log:
console.log(socket.nickname);
you where missing a closing quote (') in your:
console.log('User joined chat room 1);
Im not entirely sure about your loop.
Below is the amended code should help you out a bit, also be aware the loop i am using below is asynchronous and this may effect how you handle data transfers.
socket.nickname = 'Earl';
socket.join('chatroom1');
console.log('User joined chat room 1');
var roster = io.sockets.clients('chatroom1');
roster.forEach(function(client) {
console.log('Username: ' + client.nickname);
});
to help you out more i would need to see all your code as this does not give me context.
For v4 I used this method fetchSockets()
Example :
let roomUsers=await io.in(`room-id`).fetchSockets()
see documentation here :
https://socket.io/docs/v3/migrating-from-3-x-to-4-0/#Additional-utility-methods
All the answers above and the one here socket.io get rooms which socket is currently in or here Socket.IO - how do I get a list of connected sockets/clients? were either incorrect or incomplete if you use 2.0.
In 2.0, io.sockets.manager and io.sockets.clients don't exist anymore.
Without using namespace, the following 3 parameters can all get sockets in a specific room.
socket.adapter.rooms;
io.sockets.adapter.rooms;
io.sockets.adapter.sids; // the socket.id array
With namespace (I used "cs" here), io.sockets.adapter.rooms will give a quite confusing result and the result socket.adapter.rooms gives is correct:
/* socket.adapter.rooms give: */
{
"/cs#v561bgPlss6ELZIZAAAB": {
"sockets": {
"/cs#v561bgPlss6ELZIZAAAB": true
},
"length": 1
},
"a room xxx": {"sockets": {
"/cs#v561bgPlss6ELZIZAAAB": true
},
"length": 1
}
}
/* io.sockets.adapter.rooms give: a sid without namespace*/
{
"v561bgPlss6ELZIZAAAB": {
"sockets": {
"v561bgPlss6ELZIZAAAB": true
}, "length": 1
}
}
Note: the default room is this: "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 only tried memory adapter so far, have not tried redis-adapter.
For socket.IO v3 there's a breaking change here:
Namespace.clients() is renamed to Namespace.allSockets() and now returns a Promise.
BEFORE:
// all sockets in the "chat" namespace and in the "general" room
io.of("/chat").in("general").clients((error, clients) => {
console.log(clients); // => [Anw2LatarvGVVXEIAAAD]
});
Now (v3):
// all sockets in the "chat" namespace and in the "general" room
const ids = await io.of("/chat").in("general").allSockets();
Source
In case you're not so familiar with socket.IO, it might be good to know that instead of io.of("/chat") you can write io to use the default namespace.
For Socket v.4 correct syntax would be:
const sockets = await io.in("room1").fetchSockets();
https://socket.io/docs/v4/server-api/#namespacefetchsockets
socket.io ^ 2.0
function getRoomClients(room) {
return new Promise((resolve, reject) => {
io.of('/').in(room).clients((error, clients) => {
resolve(clients);
});
});
}
...
const clients = await getRoomClients('hello-world');
console.log(clients);
Output
[ '9L47TWua75nkL_0qAAAA',
'tVDBzLjhPRNdgkZdAAAB',
'fHjm2kxKWjh0wUAKAAAC' ]
From Socket.io v3, rooms is now a protected property of Adapter so you won't be able to access it via io.sockets.adapter.rooms.
Instead use:
const clientsInRoom = await io.in(roomName).allSockets()
OR for multiple rooms
const clientsInRooms = await io.sockets.adapter.sockets(new Set([roomName, roomName2]))
For Socket.io greater than v1.0 and node v6.0+ use the following code:
function getSockets(room) { // will return all sockets with room name
return Object.entries(io.sockets.adapter.rooms[room] === undefined ?
{} : io.sockets.adapter.rooms[room].sockets )
.filter(([id, status]) => status) // get only status = true sockets
.map(([id]) => io.sockets.connected[id])
}
If you want to emit something to them , use this :
getSockets('room name').forEach(socket => socket.emit('event name', data))
This solution is for
socket.io : "3.0.4"
socket.io-redis : "6.0.1"
import these first
const redis = require('socket.io-redis');
io.adapter(redis({ host: 'localhost', port: 6379 }));
socket.on('create or join', function(room) {
log('Received request to create or join room ' + room);
//var clientsInRoom = io.sockets.adapter.rooms[room];
mapObject = io.sockets.adapter.rooms // return Map Js Object
clientsInRoom = new Set(mapObject.get(room))
var numClients = clientsInRoom ? clientsInRoom.size : 0;
log('Room ' + room + ' now has ' + numClients + ' client(s)');
https://socket.io/docs/v3/using-multiple-nodes/#The-Redis-adapter
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get
In socket.IO 4.x
const getConnectedUserIdList = async () => {
let connectedUsers = [];
let roomUsers = await io.in(`members`).fetchSockets();
roomUsers.forEach((obj) => {
connectedUsers.push(obj.request.user.id);
});
return connectedUsers;
};
I just logged all sockets in a room to the console, you can do whatever you like with them...
const socketsInRoom = io.adapter.rooms[room_name];
/*Collect all participants in room*/
for(let participant in socketsInRoom){
for(let socketId in socketsInRoom[participant]){
console.log(socketId)
}
}
you can use the adapter method on io object like
io.sockets.adapter.rooms.get("chatroom1")
this will return the list of connected clients in the particular room.
io.sockets.adapter.rooms this is a map off all clients connected to rooms with room name as keys and the connected clients are the values of the room key. map functions are applicable .
socket.io ^2.2.0
const socket = io(url)
socket.on('connection', client => {
socket.of('/').in("some_room_name").clients((err, clients) => {
console.log(clients) // an array of socket ids
})
})
Since I have found very little on how to get the rooms inside a specific namespace, here it is in case anyone is wondering :
io.of(namespaceName).adapter.rooms;
let sockets = await io
.of("/namespace")
.in(ROOM_NAME)
.allSockets();
you can get length of connected clients via
console.log(receiver.size);
You can create an array object of user collection as
var users = {};
then on server side you can add it as new user when you connect
socket.on('new-user', function (username) {
users[username] = username;
});
while displaying the users, you can loop the "users" object
On Client side
var socket = io.connect();
socket.on('connect', function () {
socket.emit('new-user', 'username');
});