doublesidedstickytape doublesidedstickytape - 4 months ago 15
Node.js Question

SOCKET.IO - Usage of socket.id in two different browsers for same logged in user

This is more of a question regarding what to do in the scenario where you want to trigger a socket event for one user, that might be logged into another browser.

I've got a couple of functions that update a users' workstack real-time (in a queue of other workstacks that are assignable by other users); however, if the user is logged into another browser at the same time, and do an update in one browser, it doesn't update in the other (as they have a different socket.id).

I'm not sure what to do with this... I could do it based on the user ID of the person logged in, but at present my socket code does not have scope of any session variables and although there are modules such as session-socket - I'm not sure if that's the right path to go down.

Can anyone advise a way I might approach this please?

Answer

If you don't use any cluster you can follow the approach I had in my Miaou chat : I simply set the user as a property of the socket object and I iterate on sockets when necessary. This allow for a few utilitarian functions.

Here's the (simplified) related code.

io.on('connect', function(socket){
    ...
    var userId = session.passport.user;
    if (!userId) return die("no authenticated user in socket's session");
    ...
    var shoe = new Shoe(socket, completeUser) // <=== bindind user socket here 

    // socket event binding here

The Shoe object :

// A shoe embeds a socket and is provided to controlers and plugins.
// It's kept in memory by the closures of the socket event handlers
function Shoe(socket, completeUser){
    this.socket = socket;
    this.completeUser = completeUser;
    this.publicUser = {id:completeUser.id, name:completeUser.name};
    this.room;
    socket['publicUser'] = this.publicUser;
    this.emit = socket.emit.bind(socket);
}
var Shoes = Shoe.prototype;



// emits something to all sockets of a given user. Returns the number of sockets
Shoes.emitToAllSocketsOfUser = function(key, args, onlyOtherSockets){
    var currentUserId = this.publicUser.id,
        nbs = 0;
    for (var clientId in io.sockets.connected) {
        var socket = io.sockets.connected[clientId];
        if (onlyOtherSockets && socket === this.socket) continue;
        if (socket && socket.publicUser && socket.publicUser.id===currentUserId) {
            socket.emit(key, args);
            nbs++;
        }
    }
    return nbs;
}

// returns the socket of the passed user if he's in the same room
Shoes.userSocket = function(userIdOrName) {
    var clients = io.sockets.adapter.rooms[this.room.id],
        sockets = [];
    for (var clientId in clients) {
        var socket = io.sockets.connected[clientId];
        if (socket && socket.publicUser && (socket.publicUser.id===userIdOrName||socket.publicUser.name===userIdOrName)) {
            return socket;
        }       
    }
}

// returns the ids of the rooms to which the user is currently connected
Shoes.userRooms = function(){
    var rooms = [],
        uid = this.publicUser.id;
        iorooms = io.sockets.adapter.rooms;
    for (var roomId in iorooms) {
        if (+roomId!=roomId) continue;
        var clients = io.sockets.adapter.rooms[roomId];
        for (var clientId in clients) {
            var socket = io.sockets.connected[clientId];
            if (socket && socket.publicUser && socket.publicUser.id===uid) {
                rooms.push(roomId);
                break;
            }
        }   
    }
    return rooms;
}


// returns the first found socket of the passed user (may be in another room)
function anyUserSocket(userIdOrName) {
    for (var clientId in io.sockets.connected) {
        var socket = io.sockets.connected[clientId];
        if (socket.publicUser && (socket.publicUser.id===userIdOrName||socket.publicUser.name===userIdOrName)) {
            return socket;
        }
    }
}

// closes all sockets from a user in a given room
exports.throwOut = function(userId, roomId, text){
    var clients = io.sockets.adapter.rooms[roomId];;
    for (var clientId in clients) {
        var socket = io.sockets.connected[clientId];
        if (socket.publicUser && socket.publicUser.id===userId) {
            if (text) socket.emit('miaou.error', text);
            socket.disconnect('unauthorized');
        }
    }
}

Real code

Now, with iojs and WeakMap, I might implement a more direct mapping but the solution I described is robust and efficient enough.

Comments