Introduction à XMPP avec ejabberd

GNU/Linux Magazine n° 126 | avril 2010 | Damien Jégou
Creative Commons
  • Actuellement 0 sur 5 étoiles
0
Merci d'avoir participé !
Vous avez déjà noté cette page, vous ne pouvez la noter qu'une fois !
Votre note a été changée, merci de votre participation !
XMPP est un protocole qui a récemment fêté ses 10 ans, pourtant son utilisation reste encore confidentielle. Cantonné dans son rôle de protocole de discussion instantanée, il offre bien d'autres possibilités grâce à de nombreuses extensions, qu'elles fassent partie de la norme ou non. Cette capacité à intégrer facilement de nouvelles fonctionnalités lui permet de servir à des applications plus originales que la messagerie instantanée pure. Par exemple, comme base pour un site web ou pour un jeu en réseau. Cet article va vous présenter l'installation du serveur XMPP ejabberd, la création de modules côté serveur et de clients XMPP.

1. Introduction

Le réseau Jabber public est constitué de multiples serveurs répartis sur la planète. Ces serveurs communiquent entre eux, ce qui permet aux utilisateurs de n'importe quel serveur public de communiquer avec un utilisateur inscrit sur un autre serveur, mais aussi d'accéder à certains services comme pubsub (notifications) ou les salons de conférences (ou MUC rooms, pour Multi User Chat rooms). Vous pouvez aussi couper votre serveur du monde extérieur et vous en servir pour une communication strictement interne (pour un serveur d'entreprise, par exemple). Ce réseau repose sur XMPP, un protocole qui utilise principalement des flux XML.

Parmi les quelques implémentations libres de serveurs jabber existantes, mon choix s'est porté vers ejabberd. Écrit en Erlang, langage exotique mais robuste, ce serveur peut aussi bien servir à un petit groupe d'utilisateurs sur une machine de récupération qu'à mettre en place un serveur public capable de soutenir la charge de dizaines de milliers d'utilisateurs simultanés. La relative simplicité à créer des extensions et à les installer est également un critère important lorsqu'on touche à un protocole officiellement présenté comme extensible !

Un guide d'installation et configuration est disponible (en anglais) sur le site officiel. Très technique, ce sera un bon compagnon si vous souhaitez sortir des sentiers battus que vous allez découvrir dans cet article :)

2. Installation

Il est possible d'installer ejabberd depuis le gestionnaire de paquets (ce qui fonctionne très bien sous Debian SID), depuis les sources, ou en utilisant le paquet binaire indépendant de la distribution proposé sur le site officiel. Toutefois, je vous conseille fortement de suivre l'installation depuis les sources (décrite plus bas). La principale raison est que vous aurez de toute façon besoin des sources pour développer des modules (ou compiler ceux que vous trouverez dans cet article, par exemple). Dans le cas où vous voudriez simplement installer un serveur rapidement et utiliser les fonctionnalités de base (pas de modules tiers à compiler), le choix des paquets de votre distribution ne pose pas de problème, du moment que ces derniers sont bien faits.

Vous trouverez la dernière version d'ejabberd sous plusieurs formes à l'adresse suivante : http://www.process-one.net/en/ejabberd/downloads.

2.1 Depuis les sources

Assurez-vous tout d'abord que les dépendances importantes soient installées sur votre système :

  • GNU Make ;
  • GCC ;
  • Libexpat 1.95 ou supérieur ;
  • Erlang/OTP R10B-9 ou supérieur. La version recommandée est la R12B-5. Le support pour la version R13 est officiellement expérimental, bien que parfaitement fonctionnel chez moi ;
  • OpenSSL 0.9.6 ou supérieur pour le chiffrement STARTTLS, SASL et SSL. Optionnel, mais chaudement recommandé, surtout pour un serveur de production.

La liste complète est disponible dans le guide d'installation. Les sources sont disponibles sur la même page que les binaires : http://www.process-one.net/en/ejabberd/downloads. Les habitués de la compilation ne seront pas dépaysés ici, nous récupérons une archive au format tar.gz, que l'on décompresse :

$ tar xzvf ejabberd-2.1.0.tar.gz

Le script configure dispose de beaucoup d'options. Nous aurons besoin de la gestion des bases SQL pour la suite de l'article, il faut le préciser explicitement au script avec l'argument --enable-odbc=yes. Il est aussi plus propre d'installer les logiciels compilés à la main dans un endroit adapté (comme /opt/ejabberd) afin d'éviter les mélanges entre ce qui a été installé par le gestionnaire de paquets et le reste. Pour cela, utilisez l'habituelle option --prefix=$PREFIX.

Note

J'utiliserais la variable $PREFIX dans cet article pour désigner le répertoire d'installation d'ejabberd afin que vous puissiez adapter facilement les instructions quel que soit le répertoire que vous ayez choisi. Le $PREFIX par défaut est /.

  $ cd ejabberd-2.1.0
$ ./configure --enable-odbc=yes --prefix=/opt/ejabberd
$ make

Le script configure appelé avec l'option --help affiche la documentation complète de ses options.

Et enfin, on installe (sans oublier de passer root avant) :

# make install

3. On teste !

3.1 Démarrer le serveur

Dès l'installation, votre serveur est prêt à fonctionner. Le script ejabberdctl est fourni pour faciliter les tâches d'administration usuelles. Ce script ne dispose pas de page de manuel, mais le lancer sans argument affiche une documentation assez complète.

Note

Le script ejabberdctl est installé dans $PREFIX/sbin. Si vous avez modifié le répertoire d'installation par défaut, ce script ne sera pas dans votre $PATH. Étant le seul exécutable utilisé, changer le PATH n'est pas utile, un simple alias du type alias ejabberdctl=$PREFIX/sbin/ejabberdctl fera l'affaire.

Voici un exemple d'utilisation :

# ejabberdctl status

    Failed RPC connection to the node ejabberd@localhost: nodedown

    [...]

    # ejabberdctl start

    # ejabberdctl status

    The node ejabberd@localhost is started with status: started

    ejabberd 2.1.0 is running in that node

Explication : on demande à notre script d'admin des infos à propos d'ejabberd et nous constatons que ce dernier est down. On lui intime l'ordre de se réveiller. Le script de contrôle nous rend la main sans rien dire, on vérifie alors si tout s'est bien passé. Victoire, notre serveur est à l'écoute !

3.2 Vite, un client !

Vous avez à votre disposition pléthore de clients XMPP, mais une fonctionnalité sera particulièrement utile : la console XML. Elle vous permettra de voir les flux XML qui transitent entre le client et le serveur. Je suggère donc l'utilisation de Psi. Pour les réfractaires à Qt, Gajim en version 0.13 dispose aussi d'une console XML :)

La création d'un nouveau compte est très simple, une fois le client lancé, entrez dans le menu General > Account Setup. Dans la pop-up qui s'ouvre, cliquez sur Add. Dans cette nouvelle fenêtre, indiquez un nom pour identifier ce compte (totalement indépendant de votre futur identifiant Jabber). N'oubliez pas de cocher Register New Account si vous souhaitez créer un compte sur le serveur. Le bouton Add valide ces données. Indiquez le nom (vous pouvez entrer une IP) du serveur, soit localhost a priori. Dernière étape : votre nom et mot de passe... Si tout s'est bien passé, vous disposez maintenant d'un identifiant jabber (JID) présenté comme une adresse e-mail (nom@serveur.tld). Il ne vous reste plus qu'à sauvegarder puis vous connecter à votre compte (si vous ne devinez pas comment : il suffit de passer votre statut à « Online »).

4. Configuration

La configuration du serveur est stockée dans le fichier $PREFIX/etc/ejabberd/ejabberd.conf. Ce dernier n'est pas du tout présenté à la manière des fichiers de configuration traditionnels. En fait, son contenu est constitué de structures de données telles qu'elles sont écrites en Erlang (ce qui évite d'écrire un parseur spécifique). Heureusement, la syntaxe est assez simple et lisible. Commencez par ouvrir le fichier ejabberd.cfg avec votre éditeur favori.

Pour y voir plus clair, il est nécessaire d'avoir au moins une bonne coloration syntaxique. Chez moi, Emacs détecte automatiquement le langage et colore donc le code de manière adéquate, mais ce n'est pas le cas pour Vim (et n'y voyez pas là un troll, j'utilise et apprécie les deux éditeurs :)). Voici comment activer la coloration syntaxique si ça ne fonctionne pas automatiquement chez vous.

4.1 Emacs

Pour activer le mode Erlang : [M]+[x] erlang-mode. Pour activer la coloration syntaxique, si vous ne l'avez pas déjà : [M]+[x] font-lock-mode. Le mode doit bien entendu être installé au préalable, le paquet se nomme naturellement erlang-mode sous Debian.

4.2 Vim

Ici, vous avez un mémo de la commande à la dernière ligne du fichier : :set filetype=erlang tabstop=8 foldmarker=%%%',%%%. foldmethod=marker. Si la coloration n'est toujours pas active, :syntax on devrait régler le problème. Vous constaterez qu'outre la coloration syntaxique, la première commande configure également le repliage du code, ce qui rend le travail sur le fichier plus confortable malgré sa longueur.

4.3 Syntaxe du fichier

Une fois votre éditeur bien configuré, nous pouvons commencer à détailler la syntaxe. Tout d'abord, les commentaires sont indiqués par le caractère %, qui se comporte exactement comme # en shell et d'autres langages de script : tout ce qui suit ce signe est un commentaire jusqu'à la fin de la ligne.

Les tuples, ensuite, sont délimités par des accolades et contiennent des éléments séparés par des virgules : {foo, bar}. Attention à conserver toujours le même nombre d'éléments dans un tuple donné.

Les listes sont délimitées par des crochets et leurs éléments sont séparés par des virgules, comme pour les tuples. En revanche, elles peuvent contenir un nombre variable d'éléments : [foo, bar].

Dans ce fichier, toutes les entrées de base sont des tuples suivis d'un point. Le point en Erlang a une signification précise, comparable au point que nous mettons à la fin de nos phrases lorsque nous écrivons (il termine une fonction). Dans le cadre de cet article, vous pouvez vous contenter de savoir qu'il faut un point à la fin de chaque structure de données déclarée dans le fichier.

4.3 Paramètres principaux

Avant de démarrer, il est nécessaire de connaître quelques options importantes du fichier de configuration :

  • loglevel : classique, 6 niveaux de détails numérotés de 0 à 5, le niveau 5 (debug) est bien entendu recommandé pour le développement.
  • hosts : la liste des domaines servis par ejabberd. Il est possible de servir plusieurs domaines avec des configurations spécifiques via les virtual hosts (non décrits dans cet article).
  • auth_method : choix du type de stockage pour les comptes utilisateurs, par défaut réglé sur internal, les comptes sont stockés dans une base Mnesia, un système de gestion de base de données spécifique à Erlang, fournie avec OTP (Open Telecom Platform), le framework distribué avec Erlang.
  • language : la langue par défaut pour les messages du serveur.

5. Ajout de modules

L'ajout de modules permet d'étendre facilement les fonctionnalités du serveur, que ce soit pour améliorer le support des extensions au protocole XMPP (les XEP, pour XMPP Extension Protocols) ou pour y ajouter des comportements non standards. Voici le code d'un module stupide :

-module(mod_myecho).

-behavior(gen_server).

-behavior(gen_mod).

-export([start_link/2]).

-export([start/2, stop/1, init/1, handle_call/3, handle_cast/2,

         handle_info/2, terminate/2, code_change/3]).

-export([send_packet/3]).

-include("ejabberd.hrl").

-include("jlib.hrl").

-define(PROCNAME, mod_echo).

-define(BOTNAME, echo_bot).

start_link(Host, Opts) ->

    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),

    gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).

start(Host, Opts) ->

    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),

    ChildSpec = {Proc,

        {?MODULE, start_link, [Host, Opts]},

        temporary,

        1000,

        worker,

        [?MODULE]},

    supervisor:start_child(ejabberd_sup, ChildSpec).

stop(Host) ->

    Proc = gen_mod:get_module_proc(Host, ?PROCNAME),

    gen_server:call(Proc, stop),

    supervisor:terminate_child(ejabberd_sup, Proc),

    supervisor:delete_child(ejabberd_sup, Proc).

init([Host, Opts]) ->

    ?DEBUG("mod_myecho: Starting...", []),

    ejabberd_hooks:add(user_send_packet, Host, ?MODULE,

                       send_packet, 30),

    {ok, Host}.

handle_call(stop, _From, Host) -> {stop, normal, ok, Host}.

handle_cast(_Msg, Host) -> {noreply, Host}.

handle_info(_Msg, Host) -> {noreply, Host}.

code_change(_OldVsn, Host, _Extra) -> {ok, Host}.

terminate(_Reason, Host) ->

    ?DEBUG("mod_myecho: Stopping...", []),

    ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,

                          send_packet, 30),

    ok.

send_packet(From, To, {xmlelement, "message", _, _} = Packet) ->

    JID = jlib:make_jid("echobot", "localhost", ""),

    case xml:get_tag_attr_s("type", Packet) of

 "chat" ->

     ejabberd_router:route(JID, From, Packet)

    end;

send_packet(_, _, _) ->

ok.

Ce morceau de code ne fait que vous renvoyer une copie de tous les messages de type « chat » que vous expédiez. Enregistrez ce fichier sous le nom mod_myecho.erl. Le nom de module indiqué et le nom de fichier sans extension doivent toujours être identiques. Pour le compiler, il vous faut les sources de la version d'ejabberd que vous utilisez (ceux qui seraient passés par une installation binaire peuvent se reporter à la partie 2). Appelez le compilateur par cette commande : erlc -I /path/to/ejabberd-2.1/src/ mod_myecho.erl (pensez à adapter le chemin vers les sources). Ignorez les trois warnings, puis copiez le fichier mod_myecho.beam dans le dossier contenant les autres modules du serveur (par défaut, $PREFIX/lib/ejabberd/ebin/).

Avant de pouvoir tester ce module, il reste encore à éditer le fichier de configuration pour qu'ejabberd le charge au démarrage. Vers la fin du dit fichier, vous trouverez la section modules. Dans l'extrait de fichier suivant, les ajouts sont en rouge :

{modules,

[

  ...

{mod_archive_odbc, []},

{mod_myecho, []}

]}.

N'oubliez pas d'ajouter la virgule de séparation sur le dernier module de la liste (qui devient donc avant-dernier suite à cette modification, vous suivez toujours ?). Il est temps de redémarrer ejabberd :

    # ejabberdctl stop

    # ejabberdctl start

Pour tester, connectez-vous simplement avec votre compte et envoyez-vous un message, vous apparaissez dans votre propre liste de contacts avec Psi (avec Gajim [Ctrl]+[N], puis saisissez votre propre JID).

6. Changer de base de données

Outre Mnesia, ejabberd permet de stocker les comptes utilisateurs et autres données dans une base externe. Le choix de la base est assez large : plusieurs bases SQL, LDAP et PAM sont proposées et si ça ne suffit pas, vous pouvez écrire votre propre script pour aller chercher les comptes utilisateurs où bon vous semble. Je vais décrire l'utilisation d'une base PostgreSQL à la place de Mnesia pour l'exemple. Ensuite, vous devriez pouvoir vous débrouiller avec MySQL, LDAP et autres.

Note

Bien que tous ces back-ends supportent la lecture, ce n'est pas toujours le cas de l'écriture. Autrement dit, tous ne permettent pas aux utilisateurs de créer un compte ou de changer de mot de passe via XMPP. Si vous êtes dans ce cas, il faudra mettre en place un système complémentaire pour rendre ces fonctionnalités accessibles aux utilisateurs (via une interface web, par exemple).

En plus du support ODBC, la connexion à PostgreSQL nécessite un petit module supplémentaire, voici les étapes de son installation :

$ svn co http://svn.process-one.net/ejabberd-modules/pgsql

    $ cd pgsql/trunk

    $ ./build.sh

    $ cp ebin/* ${PREFIX}/lib/ejabberd/ebin/.

Encore une fois, adaptez les chemins à votre installation. Maintenant, dans le fichier ejabberd.cfg, rendez-vous dans la section authentication pour y remplacer la ligne {auth_method, internal}. par {auth_method, odbc}.. Ensuite, dans la section Database setup, ajoutez (ou décommentez) la ligne suivante : {odbc_server, {pgsql, "host", "DBName", "DBUser", "Password"}}.. Si votre base n'écoute pas sur le port par défaut, vous pouvez l'indiquer : {odbc_server, {pgsql, "server", 1234, "database", "username", "password"}}.. Vous pouvez aussi préciser le nombre de connexions à établir vers PostgreSQL. Dans le cas d'un serveur local destiné aux tests, une seule devrait largement suffire : {odbc_pool_size, 1}.

Il ne reste plus qu'à (re)démarrer ejabberd et Postgresql.

7. Développer un client

Il existe une vaste collection de bibliothèques permettant de faciliter la création de clients XMPP. Une liste assez complète est tenue à jour sur le site de la XSF : http://xmpp.org/software/libraries.shtml. Certains projets n'étant plus activement maintenus, vérifiez bien l'activité récente dans les dépôts de code. Toutefois, la stabilité du protocole de base vous permet de travailler sans problème avec des briques logicielles conçues il y a plusieurs années.

7.1 SleekXMPP

Le premier exemple est encore un simple echobot, cette fois en Python. Le choix de l'exemple n'est pas très original, mais il a le mérite de permettre un exemple pratique, tout en étant très concis (recopier du code n'étant pas une tâche particulièrement passionnante !). Mon choix s'est porté vers la bibliothèque SleekXMPP, qui est un projet récent mais prometteur.

Vu sa jeunesse, il est peu probable que vous trouviez des packages pour cette bibliothèque, il va donc falloir récupérer le code depuis le dépôt git (pas non plus d'archive tar publiée à ma connaissance) à cette adresse : http://github.com/fritzy/SleekXMPP. Pour ce faire, exécutez simplement la commande suivante dans le répertoire de votre choix (git doit être installé au préalable : paquet git-core sous Debian) : $ git clone git://github.com/fritzy/SleekXMPP.git. Notez qu'elle est écrite en Python 3, mais qu'il est possible de l'utiliser dans des scripts en Python 2.6 (ceux qui sont encore en 2.5 devront se mettre à jour).

import sleekxmpp

class Echo(sleekxmpp.ClientXMPP):

    def __init__(self, jid, password):

        sleekxmpp.ClientXMPP.__init__(self, jid, password)

        self.add_event_handler("session_start", self.start)

        self.add_event_handler("message", self.message)

                

    def start(self, event):

        self.sendPresence()

                

    def message(self, msg):

        msg.reply("Thanks for sending\n%(body)s" % msg).send()

xmpp = Example("user@localhost/sleekxmpp", "cmplxP@ss")

if xmpp.connect():

    xmpp.process(threaded=False)

    print("done")

else:   

    print("Unable to connect.")

  

Les habitués de Python devraient comprendre aisément ce code, pour ceux qui sont moins habitués, quelques détails ne feront pas de mal. Juste après avoir importé sleekxmpp, on crée une classe Echo héritant de ClientXMPP. Dans cette classe, on commence par définir notre propre constructeur qui fait lui-même appel à son homologue de la classe parente (ligne 5), puis on ajoute au gestionnaire d'événements deux callbacks (lignes 6 et 7), qui seront appelés respectivement lors du début de session et de la réception d'un message. Les deux fonctions suivantes sont ces callbacks. Le premier est assez explicite : il indique officiellement sa présence au serveur (ne pas l'envoyer permet d'être connecté au serveur sans que vos contacts n'en soient prévenus). Le second n'est pas beaucoup plus compliqué : lorsqu'un message est reçu, on envoie une réponse constituée d'un remerciement (Thanks for sending) suivi du message original. Ici, le contenu du message est extrait de la variable msg de la même manière qu'un élément de dictionnaire Python lors du formatage de chaîne (il s'agit en fait d'un autre objet qui implémente une interface similaire). Une fois la classe Echo terminée, on peut l'utiliser en indiquant son JID et son mot de passe en arguments. La connexion au serveur se fait logiquement par un appel à la méthode connect() de l'objet obtenu. Si tout se passe bien, elle l'indique en renvoyant True. Ensuite, l'appel à la méthode process() lance la boucle principale. Il ne vous reste plus qu'à envoyer un message au JID de votre bot pour voir si tout a bien fonctionné ! Si vous ne voulez pas créer deux comptes pour tester, vous pouvez vous connecter au même compte en utilisant deux ressources différentes (par exemple foo@bar/humain et foo@bar/bot).

7.2 Strophe.js

Plus original cette fois, Strophe.js est une bibliothèque écrite en javascript. Elle permet d'ajouter à une page web des fonctionnalités temps réel équivalentes à celles d'un client lourd, la connexion étant gérée côté client via BOSH, une extension à XMPP permettant d'utiliser HTTP pour se connecter au serveur.

Pour l'utiliser, il est nécessaire d'activer le module http_bind dans la configuration d'ejabberd. Dans la section Listening Ports, décommentez (ou ajoutez) un élément http_bind à la sous-section 5280 (ce nombre étant le port par lequel vous accédez aux services listés à la suite) :

{listen,

[

%% ...

  {5280, ejabberd_http, [

captcha,

                         http_bind,

                         http_poll,

                         web_admin

]}

]}.

Vérifiez également que la ligne {mod_http_bind, []}, est présente et décommentée dans la liste des modules.

Ensuite, vous aurez besoin de configurer un proxy pour permettre la connexion à ejabberd sur le même domaine que la page, les navigateurs modernes bloquant les requêtes utilisées par Strophe.js par sécurité (contre les problèmes de cross-site scripting notamment). Avec apache2, cela se fait en ajoutant simplement les lignes suivantes dans le fichier de configuration (dans le bon virtual host si vous les utilisez).

<Proxy *>

    Order deny,allow

    Allow from all

</Proxy>

ProxyPass /xmpp-httpbind http://localhost:5280/http-bind

ProxyPassReverse /xmpp-httpbind http://localhost:5280/http-bind

Vous pouvez vérifier que tout fonctionne correctement en visitant la page http://localhost/xmpp-httpbind (adaptez si vous l'installez ailleurs qu'en local). Vous devriez voir une page intitulée ejabberd mod_http_bind v1.2 (la même que si vous visitiez http://localhost:5280/http-bind).

Récupérez ensuite le code de la bibliothèque sur le site officiel : http://code.stanziq.com/strophe/. Au moment de la rédaction de l'article, la version 1.0 est disponible sous forme d'archive tar et zip. Dans cette archive, vous trouvez deux fichiers : l'un avec le code présenté normalement pour le développement (strophe.js) et l'autre avec un code comprimé (strophe.min.js) pour la production. Choisissez la plus adaptée et copiez-la dans le répertoire racine de votre serveur web (classiquement /var/www). L'exemple choisi cette fois est plus évolué qu'un simple echobot, il s'agit d'un chat simpliste intégré dans une page web. Pour l'installer, copiez le code suivant dans un fichier (par exemple, chat.html) et enregistrez-le au même endroit :

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"

    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

  <title>Strophe.js web chat</title>

  <script type='text/javascript'

src='http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.js'>

</script>

  <script type='text/javascript'

src='http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js'>

</script>

  <script type='text/javascript' src='strophe.js'></script>

  <script type='text/javascript' src='chat.js'></script>

</head>

<body>

  <div id='login' style='text-align: center'>

    <form name='cred'>

      <label for='jid'>JID :</label>

      <input type='text' id='jid' />

      <label for='pass'>Password :</label>

      <input type='password' id='pass' />

      <input type='button' id='connect' value='connect' />

</form>

  </div>

  <hr />

<div id='chat'></div>

  <div id='talk'>

    <form>

      <label for='to'>To :</label>

      <input id='to' type='textarea'/>

      <label for='tosend'>Message :</label>

      <input id='tosend' type='textarea'/>

      <input type='button' id='send' value='send' />

</form>

  </div>

  <hr />

<div id='log'></div>

</body>

</html>

Reste encore à copier le script chat.js mentionné dans le header du fichier HTML :

var BOSH_SERVICE = '/xmpp-httpbind';

var connection = null;

function log(msg)

{

    $('#log').append('<div></div>')

 .append(document.createTextNode(msg));

}

function rawInput(data)

{

    log('RECV: ' + data);

}

function rawOutput(data)

{

    log('SENT: ' + data);

}

function sendPresence() {

    connection.send($pres({from: connection.jid}));

}

function onChat(message)

{

    var from = $(message).attr('from').split('/')[0];

    var msg = $(message).find('body').text();

    if (msg != "") {

 $('<div />').append(from + " : " + msg).appendTo('#chat');

    }

    return true

}

function onConnect(status)

{

    if (status == Strophe.Status.CONNECTING) {

        log('Strophe is connecting.');

    } else if (status == Strophe.Status.CONNFAIL) {

        log('Strophe failed to connect.');

        $('#connect').get(0).value = 'connect';

    } else if (status == Strophe.Status.DISCONNECTING) {

        log('Strophe is disconnecting.');

    } else if (status == Strophe.Status.DISCONNECTED) {

        log('Strophe is disconnected.');

        $('#connect').get(0).value = 'connect';

    } else if (status == Strophe.Status.CONNECTED) {

        log('Strophe is connected.');

 sendPresence();

    }

}

// Start

$(document).ready(function () {

    connection = new Strophe.Connection(BOSH_SERVICE);

    connection.rawInput = rawInput;

    connection.rawOutput = rawOutput;

    // Connect event

    $('#connect').bind('click', function () {

        var button = $('#connect').get(0);

        if (button.value == 'connect') {

            button.value = 'disconnect';

            connection.connect($('#jid').get(0).value,

                               $('#pass').get(0).value,

                               onConnect);

        } else {

            button.value = 'connect';

            connection.disconnect();

        }

    });

    // Send event

    $('#send').bind('click', function () {

     var mymsg = $('#tosend').get(0).value

     var tosend = $msg({to: $('#to').get(0).value,

          from: connection.jid,

          id: connection.getUniqueId()})

  .c('body', {})

  .t(mymsg);

     connection.send(tosend.tree());

     $('<div />').append("Me : " + mymsg).appendTo('#chat');

 });

    // Handlers

    connection.addHandler(onChat, null, 'message',

    'chat', null, null);

});

Ce script utilise des fonctions de jQuery (dont $ est le raccourci de la principale). Pour le HTML, je vous ai laissés vous débrouiller, mais cette fois, une explication s'impose ! Tout débute après le commentaire Start, où l'on commence par définir une fonction de rappel (callback) qui sera exécutée une fois la page chargée. Dans cette fonction, on crée l'objet connection, instance de Strophe.connection (BOSH_SERVICE, vous le devinez, est l'adresse relative du serveur BOSH). L'objet est accessible depuis tout le script. Ensuite, on ajoute simplement deux callbacks définis au début du script, qui serviront à logguer les données entrantes et sortantes dans l'espace qui leur est dédié sur la page. Les deux blocs de code suivants gèrent les événements liés aux deux formulaires présents sur la page. Le premier pour la connexion : lors d'un clic sur le bouton de validation, il change le nom de ce dernier (entre les valeurs « connect » et « disconnect »), puis il se connecte ou se déconnecte selon le nom que portait le bouton. Le dernier argument de la méthode connection.connect() est un callback qui est appelé lors des changements de statut de la connexion. Je m'en sers ici pour appeler la fonction sendPresence() une fois la connexion établie (cette dernière permet d'avertir le serveur que vous souhaitez apparaître en ligne. Sans cela, vous apparaissez hors ligne pour tous vos contacts et vous ne recevez pas leurs messages). L'autre événement concerne le formulaire d'envoi de message. Le clic est capturé de la même manière que dans le premier cas, puis le corps du message est construit à partir du contenu du formulaire et des fonctions de la bibliothèque Strophe.js. Décortiquons précisément ce bout de code :

var tosend = $msg({to: $('#to').get(0).value,

   from: connection.jid,

           id: connection.getUniqueId()})

              .c('body', {})

       .t(mymsg);

connection.send(tosend.tree());

$('<div />').append("Moi : " + mymsg).appendTo('#chat');

On utilise la fonction $msg, qui est un raccourci fourni par Strophe.js pour construire les messages (comme son nom l'indique :)). Elle prend en argument un objet JSON représentant les noms et valeurs des attributs de la balise XML mère. Cette première ligne donnera donc quelque chose qui ressemble à ceci en XML :

<message to='foo@localhost' from='bar@localhost' id='1234'

            xmlns='jabber:client'>

</message>

Ensuite, l'appel à ma méthode c() ajoute un enfant au nœud sur lequel elle est appelée. Ici, on ajoute une balise <body /> sans attribut et enfin, on inclut le texte du message dans la dernière balise créée via la méthode .t(). Voici un résultat possible :

<message to='foo@localhost' from='bar@localhost' id='1234'

            xmlns='jabber:client'>

<body>Vive le jardin magique !</body></message>

Les deux instructions suivantes sont plus explicites et concises : connection.send() s'occupe d'envoyer les éléments qu'on lui passe en arguments. Elle prend comme argument le résultat de toSend.tree() qui est un arbre DOM (toSend est un objet Strophe.Builder). La dernière instruction utilise jQuery pour construire un div contenant le texte du message envoyé précédé de son expéditeur.

La dernière instruction (celle qui suit le commentaire Handlers) consiste à indiquer un callback (oui, encore un) activé lors de la réception de données du serveur. Le premier argument est le nom de la fonction à appeler, les suivants sont les conditions de son évaluation, sous forme de chaînes de caractères. Ces conditions peuvent être, dans l'ordre : le namespace du XML reçu, le nom de la balise, la valeur de l'attribut type, celle de l'attribut id ou l'expéditeur (attribut from). Les arguments ayant la valeur null permettent de ne pas préciser de condition. Ici, on indique qu'il faut exécuter la fonction onChat lorsqu'on reçoit une balise XML message ayant un attribut type dont la valeur est « chat ». C'est cette fonction qui permet de capturer les messages reçus et de les afficher dans le div prévu à cet effet.

Note

Les callbacks ajoutés par la méthode addHandler ne sont plus appelés lorsqu'ils renvoient false. Si vous voulez que votre callback soit évalué à chaque fois que les conditions sont remplies, il faut qu'il renvoie toujours le booléen true.

Une fois ces fichiers créés au bon endroit, vous pourrez tester le chat en entrant votre JID et votre mot de passe. Lors de la connexion, vous verrez des masses de texte s'accumuler dans le bas de la page. Il s'agit des logs de connexion, bien pratiques pour déboguer. Une fois connecté, vous pouvez tester l'envoi de message via le second formulaire, qui vous demande simplement le JID de votre destinataire et le contenu de votre message. Les messages reçus s'afficheront au-dessus de ce formulaire, dans l'ordre d'arrivée. A vous ensuite d'améliorer l'exemple ou de développer autre chose ;)

Conclusion

Cet article touche à sa fin, j'espère qu'il vous aura permis de mieux connaître XMPP ou du moins une partie de son écosystème ! Dans le cadre de cette introduction, nous n'avons touché qu'aux fonctionnalités de base, mais l'exploration des XEP et de leurs implémentations vous permettra de vous faire une idée plus précise des possibilités du protocole. Si vous souhaitez allez plus loin, je vous recommande la lecture de XMPP: The definitive guide. Ce livre (en anglais) permet de s'imprégner des bases du protocole (présence, chat, ...) et des extensions les plus populaires (Publish/Subscribe) ou prometteuses (Jingle).