Working with multiple tabs with Socket.io

I’ve the following code working in my server-side, it’s all ok. But, I want to keep the same connection between n tabs, because when I open a new tab, looks like I’ve disconnected from the first tab… So, how can I keep the same connection?

client.js

socket.emit("connected", {user: inputUser.val()};

app.js

var express = require("express"),
app = express(),
http = require("http").Server(app),
io = require("socket.io")(http),
users = {};

io.on("connection", function(socket) {
    socket.on("connected", function(data) {
        socket.user = data.user;

        users[socket.user] = socket;

        updateUsers();
    });

    function updateUsers() {
        io.emit("users", Object.keys(users));
    }

    socket.on("typing", function(data) {
        var userMsg = data.user;

        if(userMsg in users) {
            users[userMsg].emit("typing", {user: socket.user});
        }
    });

    socket.on("disconnect", function(data) {
        if(!socket.user) {
            return;
        }

        delete users[socket.user];

        updateUsers();
    });

});

var port = Number(process.env.PORT || 8000);

http.listen(port, function() {
    console.log("Server running on 8000!");
});

Update: The typing event above works fine… So I tried the typing event according to the answer:

var express = require("express"),
    app = express(),
    http = require("http").Server(app),
    io = require("socket.io")(http),
    users = {};

io.on("connection", function(socket) {
    socket.on("connected", function(data) {
        socket.user = data.user;

        // add this socket to the Set of sockets for this user
        if (!users[socket.user]) {
            users[socket.user] = new Set();
        }
        users[socket.user].add(socket);

        updateUsers();
    });

    function updateUsers() {
        io.emit("users", Object.keys(users));
    }

    socket.on("typing", function(data) {
        var userMsg = data.user;

        if(userMsg in users) {
            users[userMsg].emit("typing", {user: socket.user});
        }
    });

    socket.on("disconnect", function(data) {
        if(!socket.user) {
            return;
        }

        // remove socket for this user
        // and remove user if socket count hits zero
        if (users[socket.user]) {
            users[socket.user].delete(socket);
            if (users[socket.user].size === 0) {
                delete users[socket.user];
            }
        }

        updateUsers();
    });

});

var port = Number(process.env.PORT || 8000);

http.listen(port, function() {
    console.log("Server running on 8000!");
});

But it is giving the following error:

users[userMsg].emit(“typing”, {user: socket.user}); ^

TypeError: users[userMsg].emit is not a function

Update²: To fix the typing event error, I just changed to:

socket.on("typing", function(data) {
    var userMsg = data.user;

    if(userMsg in users) {
        for(let userSet of users[userMsg]) {
            userSet.emit("typing", {user: socket.user});
        }
    }
});

Answer

There is no simple way to share a single socket.io connection among multiple tabs in the same browser. The usual model for multiple tabs would be that each tab just has its own socket.io connection.

The opening of a new tab and a new socket.io connection should not, on its own, cause your server to think anything was disconnected. If your code is doing that, then that is a fault in your code and it is probably easier to fix that particular fault.

In fact, if you want to explicitly support multiple tabs and be able to recognize that multiple tabs may all be used by the same user, then you may want to change your server side code so that it can keep track of multiple sockets for a single user, rather than how it is currently coded to only keep track of one socket per user.

If your server code is really just trying to keep track of which users online, then there’s probably an easier way to do that by referencing counting each user. I will post a code example in a bit.

var express = require("express"),
app = express(),
http = require("http").Server(app),
io = require("socket.io")(http),
users = {};

io.on("connection", function(socket) {
    socket.on("connected", function(data) {
        socket.user = data.user;

        // increment reference count for this user
        if (!users[socket.user]) {
            users[socket.user] = 0;
        }
        ++users[socket.user];

        updateUsers();
    });

    function updateUsers() {
        io.emit("users", Object.keys(users));
    }

    socket.on("disconnect", function(data) {
        if(!socket.user) {
            return;
        }

        // decrement reference count for this user
        // and remove user if reference count hits zero
        if (users.hasOwnProperty(socket.user)) {
            --users[socket.user];
            if (users[socket.user] === 0) {
                delete users[socket.user];
            }
        }

        updateUsers();
    });

});

var port = Number(process.env.PORT || 8000);

http.listen(port, function() {
    console.log("Server running on 8000!");
});

If you need the users object to have the socket object in it, then you can change what is stored in the users object to be a Set of sockets like this:

var express = require("express"),
app = express(),
http = require("http").Server(app),
io = require("socket.io")(http),
users = {};

io.on("connection", function(socket) {
    socket.on("connected", function(data) {
        socket.user = data.user;

        // add this socket to the Set of sockets for this user
        if (!users[socket.user]) {
            users[socket.user] = new Set();
        }
        users[socket.user].add(socket);

        updateUsers();
    });

    function updateUsers() {
        io.emit("users", Object.keys(users));
    }

    socket.on("disconnect", function(data) {
        if(!socket.user) {
            return;
        }

        // remove socket for this user
        // and remove user if socket count hits zero
        if (users[socket.user]) {
            users[socket.user].delete(socket);
            if (users[socket.user].size === 0) {
                delete users[socket.user];
            }
        }

        updateUsers();
    });

});

var port = Number(process.env.PORT || 8000);

http.listen(port, function() {
    console.log("Server running on 8000!");
});