A description of the live chat feature on http://avatar.lupuslabs.de/contact.html
10 years ago we spent weeks to develop a website chat. We implemented a chat server in C++, a PHP library, which talked to the chat server and JavaScript streaming in an iframe. Today it is much simpler.
Today we can use XMPP and BOSH and let the web page talk to my GTalk client, which runs all the time anyway.
Today we can use XMPP and BOSH and let the web page talk to my GTalk client, which runs all the time anyway.
Here is the shopping list of technologies:
- ejabberd XMPP server with BOSH
- Strophe JavaScript XMPP library
- jQuery JavaScript utilities
- Apache web server as BOSH proxy
- Google Talk or any other Jabber client
These components do all the work. There is only some Javascript code and a little bit of plumbing required.
1. Set up ejabberd:
Download ejabberd from http://www.ejabberd.im/. The easiest way is to use the installer from http://www.process-one.net/en/ejabberd/downloads
For XMPP to work we need XMPP users. I prefer to run ejabberd with MySQL storage, because MySQL is the easiest way for me to add users and to manage the user list programatically. But the mnesia database also works.
Here is the config to use MySQL with ejabberd (to be added to ejabberd.cfg):
% {auth_method, internal}. % disabled
{auth_method, odbc}. % enabled
{odbc_server, {mysql, "localhost", "ejabberd", "mysql-user", "mysql-password"}}
Also I comment out XMPP in-band account registration, so that nobody creates users on my server:
{access, register, [{deny, all}]}.
This article explains how to create tables for ejabberd in the MySQL server: https://support.process-one.net/doc/display/MESSENGER/Using+ejabberd+with+MySQL+native+driver
2. Set up Apache as BOSH proxy:
Enable Apache modules "proxy" and "proxy_http". The debian way:
% a2enmod proxy% a2enmod proxy_http
Add to the proxy configuration (Debian: proxy.conf)
ProxyPass /xmpp-httpbind http://127.0.0.1:5280/http-bindProxyPassReverse /xmpp-httpbind http://127.0.0.1:5280/http-bind
By default accessing the proxy is only allowed for localhost. Since Browers will access it, it needs to be accessible from anywhere. Add to the proxy configuration (Debian: proxy.conf)
ProxyRequests Off
3. Create an HTML file and start programming
Download Strophe and jQuery (or use the CDN version http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js). Add references to the HTML-head:
<script type="text/javascript" src="jquery-1.4.2.min.js"></script><script type="text/javascript" src='strophe.min.js'></script>
4. Now comes the real fun: coding
We basically create a BOSH connection from Javascript to ejabberd through apache/mod_proxy:
var conn = new Strophe.Connection('/xmpp-httpbind');
Create an XMPP user in MySQL (I am using phpmyadmin) and connect with this user:
conn.connect('test@wolfspelz.de', 'secret', OnConnectionStatus);
The OnConnectionStatus function may look like:
function OnConnectionStatus(nStatus){if (nStatus == Strophe.Status.CONNECTING) {} else if (nStatus == Strophe.Status.CONNFAIL) {} else if (nStatus == Strophe.Status.DISCONNECTING) {} else if (nStatus == Strophe.Status.DISCONNECTED) {} else if (nStatus == Strophe.Status.CONNECTED) {OnConnected();}}
When the connection is established, register message handlers and send our own presence:
function OnConnected(){conn.addHandler(OnPresenceStanza, null, "presence");conn.addHandler(OnMessageStanza, null, "message");conn.send($pres());}
BTW: handlers should always return "true". Otherwise they are removed from the handler list. A message handler may look like:
function OnMessageStanza(stanza){var sFrom = $(stanza).attr('from');var sType = $(stanza).attr('type');var sBareJid = Strophe.getBareJidFromJid(sFrom);var sBody = $(stanza).find('body').text();// do something, e.g. show sBody with jQueryreturn true;}
A presence handler may be:
function OnPresenceStanza(stanza){var sFrom = $(stanza).attr('from');var sBareJid = Strophe.getBareJidFromJid(sFrom);var sType = $(stanza).attr('type');var sShow = $(stanza).find('show').text();// do something, e.g. show status icon with jQueryreturn true;}
The connection should be closed when the page unloads. Unfortunately strophe.js (at least up to version 1.0.2) disconnects asynchronously, which does not work when the page is destroyed. After some time the XMPP server will notice, that the page disappeared and will close the connection. But if you do not want to wait, then we have to force strophe to close immediately.
There is a patch, which allows for synchronous connection closing. The patch must be applied to the strophe.js file:
There is a patch, which allows for synchronous connection closing. The patch must be applied to the strophe.js file:
diff --git a/src/core.js b/src/core.js
index 5aeb06a..f79ae29 100644
--- a/src/core.js
+++ b/src/core.js
@@ -2161,7 +2161,8 @@ Strophe.Connection.prototype = {
req.date = new Date();
try {
- req.xhr.open("POST", this.service, true);
+ var async = !('sync' in this && this.sync === true);
+ req.xhr.open("POST", this.service, async);
} catch (e2) {
Strophe.error("XHR open failed.");
if (!this.connected) {
How to use the patch:
this.conn.flush();
this.conn.sync = true; // Set sync flag before calling disconnect()
this.conn.disconnect();
5. Summary
Of course, all these functions and callbacks should be prototype based and bind the instance to the closure. We should also use a model-view architecture and handle the protocol stuff in the model, while notifying the view of really important events.
Here are the files:
- contact.html - the "driver" which loads everything and produces the GUI
- model.js - the model does it, classes: Model, Room, Participant
- view.js - the view shows it, the view registers listeners with the model
- utils.js - utility classes, logging, unit test, oberver pattern
- config.js - configurations for test and production
- setup.js - selects the appropriate configuration
- style.css
- lib/strophe.js - including the above patch
- lib/jquery-1.4.2.min.js
- lib/jquery-ui-1.8.5.custom.min.js