Développer un chat temps-réel avec Socket.io (partie 2 / 3)

Pour rappel, dans la première partie nous avons développé un chat temps-réel avec Socket.io permettant d'envoyer des messages de façon synchronisée entre différents utilisateurs. Mais il nous restait de nombreuses fonctionnalités à implémenter :

  • Afficher un message lors de la connexion et la déconnexion des utilisateurs
  • Ajouter le support des noms d'utilisateurs
  • Affichage de la liste des utilisateurs connectés
  • Affichage d'un message "xxx is typing" quand un utilisateur est en train d'écrire
  • Gestion d'un historique de messages (affichage des derniers messages envoyés avant la connexion de l'utilisateur)
  • ...

Aujourd'hui, nous allons nous intéresser aux deux fonctionnalités suivantes :

  • Ajouter le support des noms d'utilisateurs
  • Afficher un message lors de la connexion et la déconnexion des utilisateurs

Base de code

Nous repartons du code de la partie 1 pour développer cette deuxième partie. Si vous n'avez pas suivi la première partie de ce tutoriel, je vous invite soit à la suivre soit à télécharger son code source sur mon compte GitHub ou de cloner la branche git correspondante :

git clone --branch part-1 https://github.com/BenjaminBini/socket.io-chat.git

Bien, commençons !

Support des noms d'utilisateurs

Un chat n'a que peu d'intérêt si on ne peut pas identifier les différents émetteurs des messages. On va donc intégrer le support des noms d'utilisateurs à notre chat. L'utilisateur devra, s'il souhaite participer à la conversation, indiquer un pseudonyme.

Formulaire de connexion

On va donc ajouter un formulaire à notre page HTML demandant à l'utilisateur son nom d'utilisateur. Lorsque l'utilisateur cliquera sur 'Login', s'il a saisi un nom d'utilisateur on lui donnera accès au chat. De plus lorsqu'il enverra un message, son message sera précédé de son nom.

Tout d'abord ajoute à notre index.html le formulaire de connexion.

<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="style.css" />
    <title>Socket.IO chat</title>
    <style>
    </style>
  </head>
  <body id="logged-out">
    <section id="chat">
      <ul id="messages"></ul>
      <form action="">
        <input id="m" autocomplete="off"  /><button>Send</button>
      </form>
    </section>
    <section id="login">
      <form action="">
        <label for="u">Username</label>
        <input id="u" autocomplete="off" autofocus />
        <p>
          <button>Login</button>
        </p>
      </form>
    </section>
    <script src="http://code.jquery.com/jquery-1.11.1.js"></script>
    <script src="/socket.io/socket.io.js"></script>
    <script src="client.js"></script>
  </body>
</html>

On a ajouté la section login et on a donné au body l'id logged-out afin de différencier l'affichage entre le mode connecté et le mode déconnecté.
En mode déconnecté, nous souhaitons afficher le formulaire par-dessus le chat et flouter le chat en CSS.
Pour cela, ajoutons ce code à style.css.

body#logged-out {
    background: rgb(223, 223, 223);
}
body#logged-out section#chat {
    filter: blur(5px);
    -webkit-filter: blur(5px);
}
body#logged-out section#login {
    opacity: 1;
}

section#login {
    transition: all 0.5s;
    opacity: 0;
    top: 45%;
    text-align: center;
    position: absolute;
    width: 100%
}
section#login label[for="u"] {
	display: block;
	font-size: 24px;
	margin-bottom: 10px;
}
section#login input#u {
	font-size: 25px;
	text-align: center;
	padding: 5px;
	border: 5px solid rgb(158, 158, 158);
}
section#login input#u:focus {
	outline: none;
}
section#login button {
	background: #e67e22;
	border: none;
	padding: 5px 80px;
	color: white;
	font-size: 20px;
	margin-top: 20px;
	cursor: pointer;
}

Reste donc à modifier l'id logged-out du body en Javascript. Lors de la soumission du formulaire, on va vérifier que le nom d'utilisateur n'est pas vide et si c'est le cas, retirer l'id du body. Par ailleurs il serait très utile à ce moment de prévenir le serveur que l'utilisateur s'est connecté et de lui fournir le nom de cet utilisateur ! On va donc émettre un événement qui sera géré par la suite par le serveur.

Rien de très sorcier ! Ajoutez ce code à client.js :

/**
 * Connexion d'un utilisateur
 */
$('#login form').submit(function (e) {
  e.preventDefault();
  var user = {
    username : $('#login input').val().trim()
  };
  if (user.username.length > 0) { // Si le champ de connexion n'est pas vide
    socket.emit('user-login', user);
    $('body').removeAttr('id'); // Cache formulaire de connexion
    $('#chat input').focus(); // Focus sur le champ du message
  }
});

Plutôt que d'envoyer simplement une chaîne de caractères contenant le nom de l'utilisateur, on l'encapsule dans un objet. En effet, si on souhaite plus tard ajouter d'autres informations sur l'utilisateur il sera plus simple d'avoir déjà un objet user de prêt.

Lancez votre application avec un petit coup de node server et testez-la (http://localhost:3000). Le formulaire de connexion s'affiche, entrez un nom d'utilisateur et validez : vous vous retrouvez bien sur le chat. Reste à gérer l'événement user-login côté utilisateur afin de conserver et d'afficher le nom de l'utilisateur aux côtés de ses messages.

Côté js

Côté serveur on va avoir besoin d'une variable user pour stocker les informations sur notre utilisateur. Cette variable est locale, elle diffère pour chaque socket (chaque utilisateur connecté). Il faut donc la placer dans la fonction de callback de l'événement 'connection'. Modifiez donc server.js.

[...]
io.on('connection', function (socket) {

  /**
   * Utilisateur connecté à la socket
   */
  var loggedUser;
[...]

Lorsque que le serveur reçoit l'événement user-login, il faut stocker l'utilisateur dans cette variable. Ajoutez donc ces quelques lignes à l'intérieur de la fonction de callback de l'événement 'connection'.

  /**
   * Connexion d'un utilisateur via le formulaire
   */
  socket.on('user-login', function (loggedUser) {
    console.log('user logged in : ' + loggedUser.username);
    user = loggedUser;
  });

On va également modifier la fonction qui gère la réception des messages. Je vous rappelle le fonctionnement : à chaque message reçu par le serveur, celui-ci émet un événement vers tous les utilisateurs avec le fameux message joint. Nous devont maintenant envoyer également le nom de l'utilisateur émetteur du message.
Modifions donc cette fonction pour y intégrer nos évolutions.

  /**
   * Réception de l'événement 'chat-message' et réémission vers tous les utilisateurs
   */
  socket.on('chat-message', function (message) {
    message.username = loggedUser.username; // On intègre ici le nom d'utilisateur au message
    io.emit('chat-message', message);
    console.log('Message de : ' + loggedUser.username);
  });

Plus qu'à gérer ce nouvel élément côté client. Lors de la réception d'un message, nous n'allons plus uniquement afficher le message mais également le nom d'utilisateur !

/**
 * Réception d'un message
 */
socket.on('chat-message', function (message) {
  $('#messages').append($('<li>').html('<span class="username">' + message.username + '</span> ' + message.text));
});

Un peu de style et c'est fini :

section#chat #messages li span.username {
	display: inline-block;
	padding: 6px 10px;
	margin-right: 5px;
	color: white;
	background: #e67e22;
	border-radius: 5px;
}

On est bons !

Testez en ouvrant plusieurs onglets et en vous connectant avec des noms d'utilisateur différents : cela fonctionne.

Affichage d'un message informant de la connexion/déconnexion d'un utilisateur

Ceci va être bien plus simple. On veut, lorsqu'un utilisateur se connecte, afficher un message dans le chat. De même lorsqu'il se déconnecte.

Côté serveur il suffit d'émettre un événement lors de la connexion d'un utilisateur et un autre lors de la déconnexion (en broadcast, c'est à dire à tous le monde sauf à l'utilisateur connecté à la socket courante).
Nous allons appelé cet événement service-message. On joint à l'événement un objet serviceMessage qui contient un type (login ou logout) et un texte (qui s'affichera en front-end.
Modifions donc le comportement du serveur lors de la réception des événements user-login et disconnect.

[...]
  /**
   * Déconnexion d'un utilisateur : broadcast d'un 'service-message'
   */
  socket.on('disconnect', function () {
    if (loggedUser !== undefined) {
      console.log('user disconnected : ' + loggedUser.username);
      var serviceMessage = {
        text: 'User "' + loggedUser.username + '" disconnected',
        type: 'logout'
      };
      socket.broadcast.emit('service-message', serviceMessage);
    }
  });

  /**
   * Connexion d'un utilisateur via le formulaire :
   *  - sauvegarde du user
   *  - broadcast d'un 'service-message'
   */
  socket.on('user-login', function (user) {
    loggedUser = user;
    if (loggedUser !== undefined) {
      var serviceMessage = {
        text: 'User "' + loggedUser.username + '" logged in',
        type: 'login'
      };
      socket.broadcast.emit('service-message', serviceMessage);
    }
  });
[...]

Côté client nous allons réceptionner cet événement service-message et l'afficher dans la liste des messages.

/**
 * Réception d'un message de service
 */
socket.on('service-message', function (message) {
  $('#messages').append($('<li class="' + message.type + '">').html('<span class="info">information</span> ' + message.text));
});

Un peu de stayyyle, parce que quand même.

section#chat #messages li.logout {
	background: #E5A6A6;
}
section#chat #messages li.login {
	background: #A8E5A6;
}
section#chat #messages li span.info {
	display: inline-block;
	padding: 3px 10px;
	margin-right: 5px;
	color: white;
	background: #e67e22;
	border-radius: 5px;
}

Redémarrez votre appli, tout devrait rouler :)


On a terminé !

Sources

Vous pouvez retrouver les sources de ce tutoriel sur GitHub. Il est possible de les télécharger directement depuis cette page ou de cloner la branche Git correspondant à cette partie du tutoriel.

git clone --branch part-2 https://github.com/BenjaminBini/socket.io-chat.git

Conclusion

En ajoutant quelques événements à droite et à gauche on peut réellement commencer à avoir simplement, en quelques lignes de javascript, une application réellement fonctionnelle et utilisable dans le monde réel.

La suite

Dans la suite nous verrons trois dernières fonctionnalités qui paraissent utiles à ajouter :

  • Affichage de la liste des utilisateurs connectés
  • Affichage d'un message "xxx is typing" quand un utilisateur est en train d'écrire
  • Gestion d'un historique de messages (affichage des derniers messages envoyés avant la connexion de l'utilisateur)

Cliquez ici pour accéder à la partie 3



Strasbourg, France

Ingénieur en informatique chez Sully Group.