Handle XMPP presence with Node

Problem

I'm using the node-xmpp module to connect to a XMPP server and join a group chat. Connecting to the server, setting the presence, joining the room and reading out messages works so far. But I want to receive the userlist of the room too.

The XMPP protocol requires to send a presence stanza when the client enters the room (http://xmpp.org/extensions/xep-0045.html#enter-pres). But how can I now parse it in node?

My code currently looks like this:

var xmpp = require('node-xmpp');

// Create the XMPP Client
var cl = new xmpp.Client({
    jid: jid,
    password: password,
    reconnect: true
});

// Do things when online
cl.on('online', function() {
  util.log("We're online!");

  // Set client's presence
  cl.send(new xmpp.Element('presence', { type: 'available' }).c('show').t('chat'));
  cl.send(new xmpp.Element('presence', { to: room_jid+'/'+room_nick }).c('x', { xmlns: 'http://jabber.org/protocol/muc' }).c('history', {seconds: 1}));

  // Send keepalive
  setInterval(function() {
    cl.send(' ');
  }, 30000);




  cl.on('stanza', function(stanza) {
      // always log error stanzas
      if (stanza.attrs.type == 'error') {
        util.log('[error] ' + stanza);
        return;
      }

      // ignore everything that isn't a room message
      if (!stanza.is('message') || !stanza.attrs.type == 'chat') {
        return;
      }

      var body = stanza.getChild('body');
      // message without body is probably a topic change
      if (!body) {
        return;
      }

    // Extract username
    var from, room, _ref;
    _ref = stanza.attrs.from.split('/'), room = _ref[0], from = _ref[1];
     var message = body.getText();

     // Log topics and messages to the console
     if(!from) {
        util.log('Topic: ' + message);
     } else {
        util.log('[' + from + ']: ' + message);
     }
    });
});

I already tried triggering presence by using

if(stanza.is('presence')) {}

within the cl.on('stanza') part but it doesn't work.

Problem courtesy of: blacktea

Solution

UPDATE: I'm describing a new method now which doesn't require the client to send requests.

Background: When the client joins a group chat, the server returns presence stanzas which contain information about the connected users to the group chat.

cl.on('stanza', function(stanza) {
    // always log error stanzas
    if (stanza.attrs.type == 'error') {
        util.log('[error] ' + stanza);
        return;
    }

    if(stanza.is('presence')){
        // We are only interested in stanzas with <x> in the payload or it will throw some errors
        if(stanza.getChild('x') !== undefined) {
            // Deciding what to do based on the xmlns attribute
            var _presXmlns = stanza.getChild('x').attrs.xmlns;

            switch(_presXmlns) {
                // If someone is joining or leaving
                case 'http://jabber.org/protocol/muc#user':
                    // Get the role of joiner/leaver
                    _presRole = stanza.getChild('x').getChild('item').attrs.role;
                    // Get the JID of joiner/leaver
                    _presJID  = stanza.getChild('x').getChild('item').attrs.jid;
                    // Get the nick of joiner/leaver
                    _presNick = stanza.attrs.from.split('/')[1];


                    // If it's not none, this user must be joining or changing his nick
                    if(_presRole !== 'none') {

                        // We are now handling the data of joinging / nick changing users. I recommend to use an in-memory store like 'dirty' [https://github.com/felixge/node-dirty] to store information of the users currentliy in the group chat.


                    } else {

                        // We are now handling the data of leaving users

                    }
                break;
            }

            return;
        }

        return;
    }

OLD METHOD

I previously described a method how to query the server for current users in the group chat. By maintaining a store where all user traffic (joining, leaving, nick changing) is stored, this is no longer required. However you could still use it to make sure the data is consistent by issues like a presence stanza was not delivered to the client correctly. That's the reason it's still described below:

To request a list with users connected to the room, you need to perform the following actions:

First send a request to the server and ask for the user list:

cl.send(new xmpp.Element('iq', {from: jid, to: room_jid, type: 'get' }).c('query', { xmlns: 'http://jabber.org/protocol/disco#items' }));

then listen for iq-stanzas, parse them and populate an array with the data:

// Catching the requested user list
if(stanza.is('iq')){
    // Fetching usernames from return data (data structure: http://xmpp.org/extensions/xep-0045.html#example-12)
    var _items = stanza.getChild('query').getChildren('item');
    var users = new Array();
    for(var i = 0; i<_items.length; i++) {
        // We are building an object here to add more data later
        users[i] = new Object();
        users[i]['name'] = _items[i].attrs.name;
    }
    console.log(util.inspect(users, {depth: null, colors: true}));
    return;
}

This will provide you with a user list. To request unique JIDs you have to probe every user. To keep the list up to date, you should remove users when they leave and add + probe when they join.

Solution courtesy of: blacktea

Discussion

There is currently no discussion for this recipe.

This recipe can be found in it's original form on Stack Over Flow.