Setting Socket.io room variables

Tags: , , , ,



I would like to store some information in the socket room variables, but am getting the following error: UnhandledPromiseRejectionWarning: TypeError: Cannot set property 'host' of undefined

This is my code:

io.on('connection', (socket, next) => {
    socket.on('create game', async () => {
        console.log('Creating game...');
        socket.username = await generateUsername();

        socket.roomId = generateId();

        socket.join(socket.roomId);

        io.sockets.adapter.rooms[socket.roomId].host = socket.username;
        io.sockets.adapter.rooms[socket.roomId].isOpen = true;
        io.sockets.adapter.rooms[socket.roomId].players = [socket.username];

        console.log('Game created! ID: ', socket.roomId);
    });
}

If I try to log socket.roomId it would return something like rBAhx0. And when I log io.sockets.adapter.rooms, I get the following:

Map {
  'PX_o3Di9sp_xsD6oAAAB' => Set { 'PX_o3Di9sp_xsD6oAAAB' },
  'rBAhx0' => Set { 'PX_o3Di9sp_xsD6oAAAB' }
}

However, when I try to log io.sockets.adapter.rooms[socket.roomId], it returns undefined. How can I fix this?

Answer

Socket.io made some breaking changes in the newest version and rooms cannot be accessed like they used to. It changed a lot of objects and arrays into Maps and Sets, you can see it in the logs you’ve posted.

Set objects are collections of values. You can iterate through the elements of a set in insertion order. A value in the Set may only occur once; it is unique in the Set’s collection.

The Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value.

Accessing properties of a Map works differently than accessing properties of a normal Object. Example:

const myMap = new Map();
myMap.set("foo", "bar");
console.log(myMap["foo"]) // undefined
console.log(myMap.get("foo")) // bar

Same applies to Sets, however in your case querying this Set in particular is probably a wrong approach, as this set only holds a collection of ids, and not actual room objects. Even if you were to get a value out of the Set, you could not access it’s properties (host, isOpen and players) since it is only a string.

The version 3.0 made accessing the list of all rooms directly impossible I’m afraid. However the adapter now has a property socketRooms, which can be used in place of it.

In order to access rooms of a socket easier, you should access them like so:

io.sockets.adapter.socketRooms(socketId);

However that would still just return a list of strings.

The simplest solution to this problem would be to create an external variable outside of the connection scope.

const rooms = {};

io.on('connection', (socket, next) => {
    socket.on('create game', async () => {
        console.log('Creating game...');
        socket.username = await generateUsername();

        socket.roomId = generateId();

        socket.join(socket.roomId);

        if (!rooms[socket.roomId]) rooms[socket.roomId] = {};
        rooms[socket.roomId].host = socket.username;
        rooms[socket.roomId].isOpen = true;
        rooms[socket.roomId].players = [socket.username];

        console.log('Game created! ID: ', socket.roomId);
    });
}


Source: stackoverflow