Using Websockets in your Plugin

Thanks to @Mr_Waffle from BitBangers for writing this article! Source: BitBangers

The Problem

Say you want your plugin to communicate between client and server using websockets. There isn't a documented (or "official") way on how to hook into the websockets API of NodeBB.

I was facing this issue with the Shoutbox plugin, and had to find a clever way to get it to work. The way I found is a bit "hackish" but actually works really well and is now actually being used by a core developer in one of his plugins. editor's note: nope, not hackish at all - this is the right way to do it! :)

The Process of finding a Solution

When digging around a bit in the NodeBB source you can see how their socket implementation works. For this we navigate to src/socket.io. In this folder we see some files. You can quickly see that index.js is controlling everything, and that all the other files are socket "namespaces" (e.g. all the socket calls that have the user prefix will be handled by the user.js file. I won't go into too many details on how this works, you'll just have to believe me on this :)

When playing around with this, you find out that when you add another js file in that folder, you'll have working sockets on that namespace! But we don't want to change anything in core... We want to do this from a plugin!

We will further investigate one of these files. I chose the modules file because I thought it would fit a Shoutbox the most.

It's some pretty basic stuff in here. SocketModules is defined as a new object, and so is every "module", like SocketModules.composer. At the very end you can see that this file sets the module.exports to the SocketModules object. This means that when you require(..) this file, you'll have access to everything in the SocketModules object, but nothing outside of it. This gave me an idea.

Exploring the Idea

From your plugin you can require(..) files from NodeBB using module.parent.require(..). In this way you can also require the modules.js file like so:

var ModulesSockets = module.parent.require('./socket.io/modules');  

ModulesSockets will now be whatever this file has given us as their export. In our case, the SocketModules object, which includes everything that was defined in the modules.js file, which are basically all the socket handlers for the composer, chat, etc... Here I enter my idea: What happens if I add my own custom sockets to this object? I simply tested this with something like this (from within my plugin):

var ModulesSockets = module.parent.require('./socket.io/modules');  
ModulesSockets.test = function(socket, data, callback) {  
    console.log("Working?");
    console.log(data);
    callback(null, "It worked!");
}

Then, on the client, I entered something like this in the console:

socket.emit('modules.test', {data: "Some data"}, function(err, result) {  
    alert(result);
});

And hit enter.

And it worked! I saw the logs in the NodeBB log and was alerted with "It worked!".
Using all this information I came up with the following solution.

The Solution

So the solution is quite easy. All you have to do is require the namespace you want to extend, and add your custom handlers. You could even replace the default handlers with your own in this way!
In a bit of code:

var ModulesSockets = module.parent.require('./socket.io/modules');  
...
//I prefer to execute this in the "action:app.load" handler method
ModulesSockets.shoutbox = Shoutbox.sockets; //in this case, my handlers will listen to "modules.shoutbox.*"  
...
Shoutbox.sockets = {  
    //Bunch of socket handlers here
}

I hope you guys learned something from this :D If you have any corrections or enhancements please call me out on it :P

Schamper

Read more posts by this author.