How To: Using websockets in your plugin

NodeBB Plugins
  • I wrote this on our forum, thought it'd be nice to share it with you guys as well.
    Source: link

    #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.

    #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 😄 If you have any corrections or enhancements please call me out on it 😛

  • Hey @Mr_Waffle would you like this guide to be a guest post on our blog? Maybe omit the initial bits and get straight to the solution etc.

    Thanks for the guide 🙂

  • @psychobunny said:

    Hey @Mr_Waffle would you like this guide to be a guest post on our blog? Maybe omit the initial bits and get straight to the solution etc.

    Thanks for the guide 🙂

    Sure, go ahead 🙂

  • Thanks @Mr_Waffle !

    I started implementing this method in my static page plugin. I actually saw it in your shoutbox code on github (and stole it 😉 ) before you wrote this how to.

  • I was just about to ask about this, thanks for the guide! Just to be thorough, maybe include how to send a message in the other direction, server to client? If plugin authors are starting to append to existing modules in the core, maybe we should establish some namespacing conventions?

  • Server can do socket.emit and client can listen to socket.on without too much extra work. Keep me up to date with what you're doing and I can lend you a hand if you run into any problems 🙂

  • Yeah, I know, I was able to figure that part out 🙂 I was just saying for the sake of anyone starting out. It's not as straightforward as socket.io demonstrates...
    @Mr_Waffle 's Shoutbox plugin was a good example of using something like

    var socketIndex = module.parent.require('./socket.io/index');
    socketIndex.server.sockets.emit('event:imgbed.server.rcv.end', { id: id, url: url, alt: alt} );
    

    to send a message to the client, whereas simply using socket.emit('event:imgbed.server.rvc.end', { id: "this is the id" }); will crash the server.

  • @BDHarrington7 said:

    Yeah, I know, I was able to figure that part out 🙂 I was just saying for the sake of anyone starting out. It's not as straightforward as socket.io demonstrates...
    @Mr_Waffle 's Shoutbox plugin was a good example of using something like

    var socketIndex = module.parent.require('./socket.io/index');
    socketIndex.server.sockets.emit('event:imgbed.server.rcv.end', { id: id, url: url, alt: alt} );
    

    to send a message to the client, whereas simply using socket.emit('event:imgbed.server.rvc.end', { id: "this is the id" }); will crash the server.

    Ye this is a little bit tricky, but you can figure this out by looking at some of the core code, especially the chat and new topic/reply sockets. Because you can already figure this out by looking at core code, I didn't think it'd be that important to write a guide on that as well 🙂 Creating your own sockets isn't something that you can figure out from looking at core code!

  • @pitaj Where did you find this?


Suggested Topics


  • 0 Votes
    20 Posts
    244 Views

    @crazycells custom css tab supports sass so you can simplify that to

    /* removing buttons from composer */ .composer .formatting-bar .formatting-group { [data-format="code"], [data-format="eye-slash"], [data-format="left"], [data-format="center"], [data-format="right"], [data-format="justify"], [data-format="groupedcode"], [data-format="picture"] { display: none; } }

    Haven't tested it but it should work 😆

  • 0 Votes
    6 Posts
    4k Views

    I installed and activated the plugin-sanitizehtml , restarted NodeBB as instructed, saved the default plug-in settings from ACP.
    But, now all my HTML tags have been stripped off (I can tell by looking at the source)
    What am I missing ?
    BTW, I uninstalled the Markdown-plugIn before installing the sanitizehtml plugIn.

  • 0 Votes
    3 Posts
    1k Views

    @ThingBreaker I think i tried it after disabling the default composer and markdown plugins. But it is not working. There will be no styling or anything if i save the post.

  • 0 Votes
    1 Posts
    969 Views

    I am new to NodeBB and nodebb-plugin-sso-oauth authenticates users from oauth2-server-php with an Active Directory (Samba 4) backend. "So far so good" - this plug in obviously works in that configuration, but I was not able to include more than id, displayName and email from OAuth into user NodeBB account. My OAuth solution offers a big JSON object with many data which are usable for NodeBB.

    My library.js was patched to

    OAuth.parseUserReturn = function(data, callback) { // Alter this section to include whatever data is necessary // NodeBB *requires* the following: id, displayName, emails. // Everything else is optional. // Find out what is available by uncommenting this line: console.log(data); var profile = {}; profile.id = data.id; profile.displayName = data.display_name; profile.fullname = data.user_nicename; profile.emails = data.emails; console.log(profile); // Do you want to automatically make somebody an admin? This line might help you do that... // profile.isAdmin = data.isAdmin ? true : false; // Delete or comment out the next TWO (2) lines when you are ready to proceed //process.stdout.write('===\nAt this point, you\'ll need to customise the above section to id, displayName, and emails into the "profile" object.\n==='); //return callback(new Error('Congrats! So far so good -- please see server log for details')); callback(null, profile); }

    this console log show all data as expected, so oauth module got everything, but profile.fullname is ignored by NodBB. It seems it is not enough to patch that section to get more than minimum required data for a NodeBB user.

    Any hints?