17

I'm trying to learn Socket.io by building a set of dynamically created chatrooms that emit 'connected' and 'disconnected' messages when users enter and leave. After looking at a couple of questions I've put together something functional but most of the response linked are from people who admit they've hacked together answers and I've noticed there's a more general - and recent - discussion about the right way to do this on the Socket.io repo (notably here and here)

As I'm such a novice I don't know if the work below is an acceptable way to do things or it just happens to incidentally function but will cause performance issues or result in too many listeners. If there's an ideal - and official - way to join and leave rooms that feels less clunky than this I'd love to learn about it.

Client

var roomId = ChatRoomData._id // comes from a factory


function init() {

    // Make sure the Socket is connected
    if (!Socket.socket) {
        Socket.connect();
    }

    // Sends roomId to server
    Socket.on('connect', function() {
        Socket.emit('room', roomId);
    });

    // Remove the event listener when the controller instance is destroyed
    $scope.$on('$destroy', function () {
        Socket.removeListener('connect');
    });

}

init();

Server

  io.sockets.once('connection', function(socket){
    socket.on('room', function(room){     // take room variable from client side
      socket.join(room) // and join it

      io.sockets.in(room).emit('message', {      // Emits a status message to the connect room when a socket client is connected
        type: 'status',
        text: 'Is now connected',
        created: Date.now(),
        username: socket.request.user.username
      });

      socket.on('disconnect', function () {   // Emits a status message to the connected room when a socket client is disconnected
        io.sockets.in(room).emit({ 
          type: 'status',
          text: 'disconnected',
          created: Date.now(),
          username: socket.request.user.username
        });  
      })
  });
Community
  • 1
  • 1
isdn
  • 381
  • 1
  • 3
  • 15

4 Answers4

27

Socket.IO : recently released v2.0.3

Regarding joining / leaving rooms [read the docs.]

To join a room is as simple as socket.join('roomName')

//:JOIN:Client Supplied Room
socket.on('subscribe',function(room){  
  try{
    console.log('[socket]','join room :',room)
    socket.join(room);
    socket.to(room).emit('user joined', socket.id);
  }catch(e){
    console.log('[error]','join room :',e);
    socket.emit('error','couldnt perform requested action');
  }
})

and to leave a room, simple as socket.leave('roomName'); :

//:LEAVE:Client Supplied Room
socket.on('unsubscribe',function(room){  
  try{
    console.log('[socket]','leave room :', room);
    socket.leave(room);
    socket.to(room).emit('user left', socket.id);
  }catch(e){
    console.log('[error]','leave room :', e);
    socket.emit('error','couldnt perform requested action');
  }
})

Informing the room that a room user is disconnecting

Not able to get the list of rooms the client is currently in on disconnect event

Has been fixed (Add a 'disconnecting' event to access to socket.rooms upon disconnection)

 socket.on('disconnect', function(){(
    /*
      socket.rooms is empty here 
      leaveAll() has already been called
    */
 });
 socket.on('disconnecting', function(){
   // socket.rooms should isn't empty here 
   var rooms = socket.rooms.slice();
   /*
     here you can iterate over the rooms and emit to each
     of those rooms where the disconnecting user was. 
   */
 });

Now to send to a specific room :

// sending to all clients in 'roomName' room except sender
  socket.to('roomName').emit('event', 'content');

Socket.IO Emit Cheatsheet

Basil Victor
  • 111
  • 2
  • 13
EMX
  • 6,066
  • 1
  • 25
  • 32
  • What if someone is already in the room.. then how to alert the other person that someone is in the room already... – DragonFire Feb 16 '21 at 03:15
0

This is how I inform users of a "disconnecting user"

socket.on('disconnecting', function(){
        console.log("disconnecting.. ", socket.id)
        notifyFriendOfDisconnect(socket)
    });

function notifyFriendOfDisconnect(socket){
    var rooms = Object.keys(socket.rooms);
    rooms.forEach(function(room){
        socket.to(room).emit('connection left', socket.id + ' has left');
    });
}
Sssssuppp
  • 683
  • 1
  • 7
  • 29
0

For anyone reading this beyond 2/1/2021, using socket.io 3.1.0 and hopefully later, you can reference my example. I have found that the example on how to do this in socket.io's documentation is incorrect. They claim that in the disconnect event that socket.rooms is an object. While is uses the block container and is comma separated, there are no key pairs, meaning their demonstration of const rooms = Object.keys(socket.rooms) returns an empty value. It's creating an array out of an object that is really just an array. To my knowledge {} can only be used for block statements and objects. By some quirk, NodeJS is treating it like a normal array. I assign custom, 4 digit rooms on each connect event. So I have on the disconnect event I have the server skim through all the rooms, and if it encounters a room name with a length of 4, it tells everyone in the room that the size of the room decreased by one. On the client side, I have a socket event listener monitoring for this and when it's detected, updates a

innerHTML property so that the clients can display the number of connected users.
//client side code:
socket.on('roomSize', (roomSize) => {
    document.getElementById('clientCount').innerHTML = roomSize + ' clients connected';
});


//Server side code:
io.on("connection", (socket) => {

    socket.on('createRoom', () => {
        let ID = makeID(4)
        while (io.sockets.adapter.rooms.has(ID)) {
            ID = makeID(4)
        }
        socket.join(ID);
        socket.emit('setID', ID);
    });

    socket.on("joinRoom", (room) => {
        socket.join(room);
        let roomSize = io.sockets.adapter.rooms.get(room).size
        io.in(room).emit('roomSize', roomSize);
        socket.emit('roomJoined', room, roomSize);
    });

    socket.on('disconnecting', function() {
        let rooms = socket.rooms;
        rooms.forEach(function(room) {
            if (room.length === 4) {
                let roomSize = io.sockets.adapter.rooms.get(room).size - 1
                io.in(room).emit('roomSize', roomSize);
            }
        });

    });

    function makeID(length) {
        var result = '';
        var characters = '0123456789';
        var charactersLength = characters.length;
        for (var i = 0; i < length; i++) {
            result += characters.charAt(Math.floor(Math.random() * charactersLength));
        }
        return result;
    }
})
DragonFire
  • 3,722
  • 2
  • 38
  • 51
0

Here is a working method.

I am using socketio "socket.io": "^2.3.0" on server side. On Client side is android

// SocketIO
implementation('io.socket:socket.io-client:1.0.0') {
    // excluding org.json which is provided by Android
    exclude group: 'org.json', module: 'json'
}

Following is the code that is working for me.

// Join Chat Room
socket.on('ic_join', function(data) {

    // Json Parse String To Access Child Elements
    let messageJson = JSON.parse(data)
    let room1 = messageJson.room1

    console.log('======Joined Room========== ')
    console.log(room1)

    socket.join(room1, function(err) {
        console.log(io.sockets.adapter.rooms[room1].length);
        console.log(err)
    })
})

// Leave Chat Room
socket.on('ic_leave', function(data) {

    // Json Parse String To Access Child Elements
    let messageJson = JSON.parse(data)
    let room1 = messageJson.room1

    console.log('======Left Room========== ')
    console.log(room1)

    socket.leave(room1, function(err) {
        if (typeof io.sockets.adapter.rooms[room1] !== 'undefined' && io.sockets.adapter.rooms[room1] != null) {
            console.log(io.sockets.adapter.rooms[room1].length);
            console.log(err)
        } else{
            console.log("room is deleted")
        }
    })
})
DragonFire
  • 3,722
  • 2
  • 38
  • 51