DEV Community

Karl Stolley
Karl Stolley

Posted on • Originally published at stolley.dev on

Dynamic Namespaces in Socket.io

I’m teaching socket.io as a convenient WebRTC signaling channel in my WebRTC class this semester. As part of prepping that, I finally had to sit down and figure out dynamic namespaces in socket.io. There is some really tricky business to namespaces, dynamic or otherwise, particularly when it comes to listening for messages and events server-side sent from connected clients on the namespace.

In short, namespaced sockets work differently on the client from how they do on the server.

On the client, we simply create a namespaced socket connection (ns, here with a Google Meet–like namespace of /jkl-mnop-qrs) and both listen and emit events on it directly:

// Client-side code (site.js)

var ns = io('/jkl-mnop-qrs'); // ordinarily set dynamically in JavaScript, somehow

ns.on('message', function(data) {
  console.log('Message received: ' + data);
});

document.querySelector('body').addEventListener('click', function(e) {
  console.log('Body was clicked');
  // the `send()` method is essentially shorthand for `emit('message', data)`;
  // that is, the `send()` method emits the `message` event for us:
  ns.send('Someone clicked the body element');
});
Enter fullscreen mode Exit fullscreen mode

In that example, the listener ns.on(...) handles incoming messages and sends a pre-determined message to the socket server when someone clicks anywhere in the <body> element. Both use the ns object created from calling the io() constructor.

On the server, though, it’s a completely different, more complicated story.

// Server-side code (app.js)

const namespaces = io.of(/^\/[a-z]{3}-[a-z]{4}-[a-z]{3}$/);

namespaces.on('connection', function(socket) {
  const namespace = socket.nsp;

  // You can emit messages directly on the `namespace` object...
  namespace.emit('message', `Successfully connected on namespace: ${namespace.name}`);

  // ...BUT--BUT, BUT, BUT--you must listen for messages coming from the clients
  // on the socket (`socket`) object, NOT the namespace:
  socket.on('message', function(data) {
    console.log('A message was received from a client: ', data);
    // AND if you want to do a broadcast emit -- which sends the message to
    // all the connected clients *except* for the sender -- you MUST use the
    // socket object (`socket`), as the `namespace` does not understand the
    // `broadcast` method:
    socket.broadcast.emit('message', data);
  });
});
Enter fullscreen mode Exit fullscreen mode

To summarize the content of the comments: you listen for connections on the namespaces that match the pattern in socket.of(...). You can emit messages on the namespace returned by namespaces.on(...), but you cannot listen for incoming messages or any other events on namespace. Instead, you listen on the socket object (socket) created on the connection event.

Additionally, if you want to broadcast a message (which sends the message to all connected clients except the sending client), you must use socket.broadcast.emit.

So a simplified version of the code above looks like this:

// Server-side code (app.js)

const namespaces = io.of(/^\/[a-z]{3}-[a-z]{4}-[a-z]{3}$/);

namespaces.on('connection', function(socket) {
  const namespace = socket.nsp;

  socket.emit('message', `Successfully connected on namespace: ${namespace.name}`);

  socket.on('message', function(data) {
    console.log('A message was received from a client: ', data);
    socket.broadcast.emit('message', data);
  });
});
Enter fullscreen mode Exit fullscreen mode

Now the namespace variable is only being used for diagnostic purposes (Successfully connected on namespace: ${namespace.name}).

Everything else is listening or emitting on the socket object (socket) returned to the initial namespaces.on(...) callback on each client connection.

The thing that is tricky to grasp (and that cost me about 3 hours of my life) is that that socket object is unique to the connection on each namespace. This is not properly reflected ANYWHERE in socket.io’s documentation, which makes me crazy. And yes, I should write something and submit a pull request. I know.

Top comments (2)

Collapse
 
tenutso profile image
Tentuso

Can anyone else confirm this? The namespace variable can also be used to emit specifically in the namespace. I was under the impression that socket.broadcast.emit would broadcast to all namespaces (except sender) while namespace.emit would not.

Collapse
 
stolley profile image
Karl Stolley

The trick is that the callback function on the namespace has its own socket, which I'm here identifying as socket. So it is scoped to the namespace.