Talking to Bot using Browser

David Koelle

September 2016

In this article by David Koelle author of the book Practical Protocols : XMPP gives step for building a very simple chat client to talk to bot using XMPP and XMPP-FTW.

(For more resources related to this topic, see here.)

Building a WebSocket enabled webserver in Node.js

First, let's create a new folder and initialize a new NPM project:

mkdir browser-chat && cd browser-chat
npm init

Answer the questions however you wish (you can just happily accept all the defaults if you want). Once completed, you'll have a saved package.json file which describes the project. As we install modules, we'll write details of these into the file so installations can be repeated by others later.

Rather than use express we're going to use the base HTTP module from Node.js, as we're only looking to deliver a couple of static assets from the server itself. Let's create a file called index.js using an editor of your choice and add the following code:

var http = require('http')
var server = http.createServer(function(req, res) {
  res.write('XMPP rocks my cotton socks!')
  res.end()
})
server.listen(3000)

Please feel free to contact the author for the usual vim vs emacs editor discussion, but be aware that vim is always the correct answer!

In the above code, we're creating an HTTP server and telling it to listen on port 3000. When a request (req) is received, we simply write our string to the response (res) object and close the connection.

If we now save and exit the file, then by running node index.js and opening our browser to http://localhost:3000 we should see a browser window which looks like this:

Practical XMP Protocols

Next we'll want to extend the code to return some static files (namely an index.html and some JavaScript files) to the user. We'll write it such that if a file exists on the file system we'll return an HTTP status code of 200, set a MIME type of text/html (unless the file is in a scripts directory, in which case we'll return application/JavaScript), and return the file contents. However, if the file is not found on the file system, then we'll just return a simple 404 response, let's dive in:

var http = require('http')
  , fs = require('fs')
  , path = require('path')
  , url = require('url')
var server = http.createServer(function(req, res) {
  var uri = url.parse(req.url).pathname
  if (uri === '/') uri = '/index.html'
  var filename = path.join(process.cwd(), 'public', uri)
  fs.exists(filename, function(exists) {
    if (!exists) {
      res.writeHead(404)
      res.end()
      return
    }
    var mimeType = (-1 === req.url.indexOf('/scripts')) ?
      'text/html' : 'application/javascript'
    res.writeHead(200, mimeType)
    fs.createReadStream(filename).pipe(res)
  })
})
server.listen(3000)

In this example, we load a set of useful modules with fs handling file system interactions, path helping us to build file system paths, and URL helping us to parse incoming URLs. Next we use the path module to obtain the requested URL path; if the value is simply a slash then we'll assume the user is requesting an index.html file for convenience. After this we use the fs module to see if the file exists on the system after building up a file system path from the processes working directory, a public subfolder, and the original requested file. Given that Node.js tries not to block on Input/Output (IO) this is an asynchronous callback.

If this file does not exist, then we write an HTTP status code of 404 (Not Found) to the headers and end the request. However, if the file is found we check for the existence of script in the request URL and set our MIME type response accordingly as well as a 200 (OK) response code. Lastly we load the file from the file system and pipe it into our response.

Got that? Groovy, then let's continue. If we create ourselves a public folder, generate an index.html file, and fire the server back up we should see our HTML being sent to the browser:

mkdir -p public/scripts
echo "<h1>XMPP rocks everyone's socks</h1>" > public/index.html
node index.js

Refreshing our browser should return the expected message. Also note that if we navigate to an unknown URL, e.g. http://localhost:3000/does-not-exist, you'll get a "not found error" in your browser.

Let's get cracking with our WebSocket connection! To do that, we'll first install a library called Primus (http://primus.io). Primus is a WebSocket abstraction layer for the, well, common Node.js WebSocket abstraction layers but it has the huge benefit of unifying their APIs and fixing some bugs in the underlying code. We're also going to make use of the ws WebSocket library (which is probably the simplest library you'll find). To install these and to save them to our package.json file we run the following command:

npm install --save primus ws

If you quickly pop open your package.json file you'll note that both these dependencies have been written inside making it easy to install the same modules later using npm install in the same directory.

Now we'll add Primus to our script and handle a new WebSocket connection and disconnection. To make things simpler, we'll simply add our new code to the end of index.js:

var Primus = require('primus')
var options = { transformer: 'websockets' }
var primus = new Primus(server, options)
primus.on('connection', function(socket) {
  console.log('New websocket connection')
})
primus.on('disconnection', function(socket) {
    console.log('Websocket disconnected')
})
primus.save(path.join(process.cwd(), 'public', 'scripts', 'primus.js'))

Here we're generating a new Primus object, linking it to our server, and then reporting on new connections and disconnections. Primus will also generate the appropriate client-side code for us which we'll save to the public/scripts directory.

Talking WebSockets from a browser

Now that we have our server all set up to listen for WebSocket connections, we can start building our browser code. For this we'll pull in a Primus client code and update our index.html to create the WebSocket connection. Our HTML file now becomes:

<!DOCTYPE html>
<html>
<head>
  <title>XMPP Client Example</title>
  <script type="text/javascript" src="/scripts/primus.js"></script>
  <script type="text/javascript">
  var socket = new Primus()
  socket.on('open', function() {
    console.log('Websocket connection achieved!')
  })
  </script>
  <script type="text/javascript" src="/scripts/xmpp.js"></script>
</head>
<body>
  <h1>Instant Answers Example Client</h1>
  <p>Send me a search question and I'll reply with an answer</p>
</body>
</html>

Firing up our webserver once again and peeking at both its output and the browser developer console you should now see messages about WebSocket connections being created. Hit refresh in the browser and you'll also see a note about the previous connection being closed before another is reopened. Exciting stuff!

Installing XMPP-FTW and getting messaging!

The last step for our server code is to install XMPP-FTW and wire it up on the server side. But before that we need a Primus plugin called primus-emitter (https://github.com/cayasso/primus-emitter), which allows use to use an event emitter with the WebSocket:

npm i -–save primus-emitter.

Then we append index.js with the following to tell Primus to make use of the plugin.

primus.use('emitter', require('primus-emitter'))

Finally, we can now install XMPP-FTW on the server side and start concentrating on the client:

npm i –-save xmpp-ftw

Now within our WebSocket connection blocks we'll need to create and destroy our XMPP-FTW sessions as follows:

var Xmpp = require('xmpp-ftw')
primus.on('connection', function(socket) {
  console.log('New websocket connection')
  var xmpp = new Xmpp.Xmpp(socket)
  socket.xmpp = xmpp
})
primus.on('disconnection', function(socket) {
  console.log('Websocket disconnected, logging user out')
  socket.xmpp.logout()
})

XMPP-FTW by default includes extensions to handle both 1-to-1 chat and presence so there's no additional code to add at this point. Now to the client side.

Chatting to XMPP bot

To chat to our bot, we'll need three sets of areas on our web page. To make the development process easier, we'll import jQuery into our webpage. To do this, we'll add a script tag to our <head> section and pull in jQuery (http://jquery.com/) from the Google CDN:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js">
</script>

Then for our page elements we'll first add a simple login form:

<div class="login">
  <label for="jid">JID</label><input type="text" name="jid" placeholder="jid" />
  <label for="password">Password</label><input type="password" name="password" />
  <p class="connection-status">Offline</p>
  <button type="button" name="login">Login</button>
</div>

Then an area to send our messages from:

<div class="send">
  <label for="outgoing-message">Message</label>
  <textarea name="outgoing-message"></textarea>
  <button type="button" name="send-message">Send Message</button>
</div>

Lastly, an area to hold incoming messages:

<div class="received"></div>

Login

The first task we'll need to achieve is to get logged into our server. Let's create a script file in our project at public/scripts/xmpp.js where we can start handling the client side features. To handle login, we'll listen for a client on the login button:

socket.on('xmpp.connection', connected)
var connected = function(details) {
  $('p.connection-status').html('Online')
}
socket.on('xmpp.error', errorReceived)
var errorReceived = function(error) {
  if ('auth' === error.type) {
    return alert('Authentication failed')
  }
}
$('button[name="login"]').click(function() {
  var jid = $('input[name="jid"]').val()
  var password = $('input[name="password"]').val()
     if (!jid || !password) {
       return alert('Please enter connection details')
     }
     var options = { jid: jid, password: password }
     socket.send('xmpp.login', options)
})

Once you've completed this you can start up the server (using node index), open your browser to http://localhost:3000/, enter the JID test@localhost and the password password, click the Login button and hopefully we should see the connection status update to read Online. Great stuff so far!

Just in case you make an error in your code/outgoing data for events which don't have a callback function its also worth listening on the xmpp.error.client event, so you'll know if anything went wrong

Interacting with the chat bot

The next thing we're going to do is interact with the server side chat bot we created earlier. To do this, we'll need to handle both sending and receiving a message, so we'll do this in two parts. To receive a message, in our xmpp.js file we'll listen for a click on the Send Message button, and output our message to the browser:

var sendMessage = function() {
  var message = $('textarea[name="outgoing-message"]').val()
  if (!message) return alert('Please enter a message!')
  var toSend = {
    to: 'bot@localhost' /* We'll hard code this for now */
    content: message
  }
  var html = [ '<div class="message">', '<time>', new Date().toString(), '</time>', '<span title="outgoing message"> -&gt; </span> ', message, '</div>' ]  
    $('div.received').append(html.join(''))
  socket.send('xmpp.chat.message', toSend)
}
$('button[name="send-message"]').click(sendMessage)

Easy right? Next we'll handle receiving a chat message:

socket.on('xmpp.chat.message', receivedMessage)
var receivedMessage = function(incoming) {
    if (('localhost' !== incoming.from.domain) || ('bot' !== incoming.from.local)) {
      return /* Ignore messages from anywhere else */
    }
    if (!incoming.content) return /* Ignore anything which isn't a chat message */
    /* Note: We really should escape the message contents here! */
   var html = [ '<div class="message">', '<time>', new Date().toString(), '</time>', '<span title="incoming message"> &lt;- </span> ', message, '</div>' ]  
    $('div.received').append(html.join(''))
  }

Now we're handling incoming and outgoing messages from our bot. Should you wish to fire up the bot and refresh your browser then, after logging, in you should be able to send and receive messages. Super cool!

Seeing what the chat bot is up to…

As we went through the effort of getting the chat bot to send chat state notifications, it would seem silly not to also display these to the end user so they know that something is happening. To do this, we'll need to add an additional section of HTML and extend our received Message function. In the HTML, we'll add a simple <p> tag after our received <div> to display the chat status as follows:

<p class="chat-status"></p>

Next, we'll update the function call just above the if (!incoming.content) line to handle chat status notifications, making the function look as follows:

var receivedMessage = function(incoming) {
    if (('localhost' !== incoming.from.domain) || ('bot' !== incoming.from.local)) {
      return /* Ignore messages from anywhere else */
    }
    handleChatState(incoming.state)
    if (!incoming.content) return /* Ignore anything which isn't a chat message */
    /* Note: We really should escape the message contents here! */
   var html = [ '<div class="message">', '<time>', new Date().toString(), '</time>', '<span title="incoming message"> &lt;- </span> ', message.replace(/\n/g, '<br/>'), '</div>' ]  
    $('div.received').append(html.join(''))
  }

We can then implement the handleChatState method to, well, handle chat state updates. Remember, we only implemented a few of the states (active, inactive, composing) in our bot, so we'll just handle these specifically:

var handleChatState = function(state) {
    if (!state) return /* Nothing to update */
    switch (state) {
      case 'active':  state = 'Reading question'; break
      case 'composing':
      default: state = 'Writing a response'; break
      case 'inactive': state = ''; break
    }
    $('p.chat-status').html(state)
}

Sweet! So now when our bot is off doing busy work we'll see that it is busy serving our requests.

Hello (hello, hello...), is there anybody out there?

Finally, it would be great to know if our bot was alive and kicking, or being lazy and taking a break from its duties and having a rest. To determine this information, we'll need to use presence and presence subscriptions. First, we'll update our code to tell the server that we're available on connection, make a presence subscription request to our bot (which it will automatically accept), and then display the presence of the bot on screen.

Let's add a little piece of HTML to index.html and default the bot to showing as offline (you just can't get the right bots sometimes!):

<p class="bot-status">Offline</p>

Now we have this code in place our first task is to send our own presence availability to the server once we've successfully logged in. We'll need to update the connected method to send our presence out:

var connected = function(details) {
  $('p.connection-status').html('Online')
  socket.send('xmpp.presence', { show: 'chat' })
}

Now we'll set up the code to listen for incoming presence status updates from the bot (remembering that the first time we run this code we won't actually have a subscription yet). Once again we add code to our xmpp.js file:

socket.on('xmpp.presence', function(presence) {
  if (('localhost' !== presence.from.domain) || ('bot' !== presence.from.local)) {
  return /* Ignore messages from anywhere else */
}
var status = 'Offline'
switch (presence.show) {
  case 'chat': status = 'ready to answer!'; break
  case 'away': 
  case 'xa':
    status = 'away.'; break
  case 'dnd': status = 'busy right now.': break
}
$('p.bot-status').html('Instant answer bot is ' + status)    
})

The very final thing we need to do to get the XMPP server to send us presence updates for the bot is to request a presence subscription. The way we'll do this is quite wasteful (since we're going to ask each and every page load regardless) but we can learn how to do this more efficiently later on. Let's jump back to our connected method and send out our presence subscription:

var connected = function(details) {
  $('p.connection-status').html('Online')
  socket.send('xmpp.presence', { show: 'chat' })
  socket.send('xmpp.presence.subscribe', { to: 'bot@localhost' })
}

That's it! Refresh your browser and start playing with the instant answer bot – like asking it what is the best Pink Floyd song. Whilst you are at it you should be able to stop and restart the bot node process and see its presence status change in real time in the browser.

Summary

In this article we discussed how to use XMPP and XMPP-FTW along with JSON to create a client chat application to talk with Bots. It also show how we can use node to chat and see what are they up to in real time. 

Resources for Article:


Further resources on this subject:


You've been reading an excerpt of:

Practical XMPP

Explore Title
comments powered by Disqus