Chat en temps réel avec Readline & Socket.io de Node.js

Ce que vous allez créer

Node.js a un module sous-estimé dans sa bibliothèque standard qui est étonnamment utile. Le module Readline fait ce qu’il dit sur la boîte: il lit une ligne d’entrée du terminal. Cela peut être utilisé pour poser une question ou deux à l'utilisateur, ou pour créer une invite au bas de l'écran. Dans ce tutoriel, j'ai l'intention de montrer les capacités de Readline et de créer un forum de discussion en temps réel CLI soutenu par Socket.io. Le client n’enverra pas seulement des messages simples, il aura également des commandes pour emotes avec /moi, messages privés avec / msg, et permettre aux surnoms d'être changé avec /entaille.

Un peu de Readline

C'est probablement l'utilisation la plus simple de Readline:

var readline = require ('readline'); var rl = readline.createInterface (process.stdin, process.stdout); rl.question ("Quel est votre nom?", fonction (réponse) console.log ("Bonjour," + réponse); rl.close (););

Nous incluons le module, créons l'interface Readline avec les flux d'entrée et de sortie standard, puis posons une question unique à l'utilisateur. Ceci est la première utilisation de Readline: poser des questions. Si vous avez besoin de confirmer quelque chose avec un utilisateur, peut-être sous la forme du très populaire "Voulez-vous le faire? (Y / n)", qui imprègne les outils de la CLI, readline.question () est le moyen de le faire.

L’autre fonctionnalité fournie par Readline est l’invite, qui peut être personnalisée à partir de sa valeur par défaut ">"Caractère et temporairement en pause pour empêcher la saisie. Pour notre client de chat Readline, ce sera notre interface principale. Il y aura une seule occurrence de readline.question () demander à l'utilisateur un surnom, mais tout le reste sera readline.prompt ().

Gérer vos dépendances

Commençons par la partie ennuyeuse: les dépendances. Ce projet utilisera socket.io, la socket.io-client paquet et ansi-couleur. Votre packages.json Le fichier devrait ressembler à ceci:

"name": "ReadlineChatExample", "version": "1.0.0", "description": "discussion en ligne de commande avec readline et socket.io", "author": "Matt Harzewski", "dépendances": "socket .io ":" dernier "," socket.io-client ":" dernier "," ansi-color ":" dernier "," privé ": vrai

Courir npm installer et vous devriez être bon pour aller.

Le serveur

Pour ce tutoriel, nous utiliserons un serveur Socket.io incroyablement simple. Cela ne devient pas plus fondamental que cela:

var socketio = require ('socket.io'); // Écouter sur le port 3636 var io = socketio.listen (3636); io.sockets.on ('connexion', fonction (socket) // Diffuse le message d'un utilisateur à tous les autres utilisateurs de la pièce socket.on ('send', fonction (données) io.sockets.emit ('message', Les données); ); );

Tout ce qu'il fait est de prendre un message entrant d'un client et de le transmettre à tous les autres. Le serveur serait probablement plus robuste pour une application à plus grande échelle, mais pour cet exemple simple, il devrait suffire.

Ceci devrait être sauvegardé dans le répertoire du projet en tant que server.js.

Le client: comprend et installation

Avant de passer à la partie amusante, nous devons inclure nos dépendances, définir des variables et démarrer l’interface Readline et la connexion socket..

var readline = require ('readline'), socketio = require ('socket.io-client'), util = require ('util'), color = require ("ansi-color"). set; var nick; var socket = socketio.connect ('localhost', port: 3636); var rl = readline.createInterface (process.stdin, process.stdout);

Le code est assez explicite à ce stade. Nous avons notre variable de surnom, la connexion socket (à travers le socket.io-client package) et notre interface Readline.

Socket.io va se connecter à localhost sur le port 3636 dans cet exemple, bien sûr, cela serait changé pour le domaine et le port de votre propre serveur si vous utilisiez une application de discussion en production. (Il n'est pas très utile de discuter avec vous-même!)

Le client: Demander le nom de l'utilisateur

Maintenant, pour notre première utilisation de Readline! Nous voulons demander à l'utilisateur le choix de son pseudo, qui l'identifiera dans la salle de discussion. Pour cela, nous allons utiliser les Readline question() méthode.

// Définir le nom d'utilisateur rl.question ("Entrez un surnom:", fonction (nom) pseudo = nom; var msg = pseudo + "a rejoint le chat"; socket.emit ('send', type: ' notice ', message: msg); rl.prompt (true););

Nous définissons la variable nick d'avant sur la valeur collectée auprès de l'utilisateur, envoyons un message au serveur (qui sera relayé aux autres clients) indiquant que notre utilisateur a rejoint le chat, puis basculons de nouveau l'interface Readline en mode invite. le vrai valeur passée à rapide() assure que le caractère d'invite est correctement affiché. (Sinon, le curseur risque de se placer sur la position zéro sur la ligne et le ">"ne sera pas montré.)

Malheureusement, Readline a un problème frustrant avec le rapide() méthode. Ca ne joue pas bien avec console.log (), qui affichera du texte sur la même ligne que le caractère d’invite, laissant ">"Caractères partout et autres bizarreries. Pour remédier à cela, nous n'utiliserons pas console.log n'importe où dans cette application, enregistrez pour un endroit. Au lieu de cela, la sortie devrait être passée à cette fonction:

fonction console_out (msg) process.stdout.clearLine (); process.stdout.cursorTo (0); console.log (msg); rl.prompt (true); 

Ce légèrement hacky solution garantit que la ligne en cours dans la console est vide et que le curseur est à la position zéro avant l'impression du résultat. Ensuite, il demande explicitement que l'invite soit à nouveau affichée, ensuite.

Donc, pour le reste de ce tutoriel, vous verrez console_out () au lieu de console.log ().

Le client: traitement des entrées

Un utilisateur peut entrer deux types d’entrées: discussion et commandes. Nous savons que les commandes sont précédées d'une barre oblique, il est donc facile de faire la distinction entre les deux.

Readline a plusieurs gestionnaires d’événements, mais le plus important est sans aucun doute ligne. Chaque fois qu'un caractère de nouvelle ligne est détecté dans le flux d'entrée (à partir de la touche Entrée ou Entrée), cet événement est déclenché. Nous devons donc nous accrocher ligne pour notre gestionnaire d'entrée.

rl.on ('ligne', fonction (ligne) if (ligne [0] == "/" && line.length> 1) var cmd = ligne.match (/ [az] + \ b /) [0 ]; var arg = line.substr (cmd.length + 2, line.length); chat_command (cmd, arg); else // envoyer un message de discussion socket.emit ('send', type: 'chat', message: line, nick: nick); rl.prompt (true););

Si le premier caractère de la ligne de saisie est une barre oblique, nous savons que c'est une commande qui nécessitera davantage de traitement. Sinon, nous envoyons simplement un message de discussion et réinitialisons l'invite. Notez la différence entre les données envoyées sur le socket ici et pour le message de jointure à l'étape précédente. C'est en utilisant un autre type, de sorte que le client destinataire sait comment formater le message et nous passons le entaille variable aussi.

Le nom de la commande (cmd) et le texte qui suit (se disputer) sont isolés avec un peu de regex et de magie de sous-chaîne, puis nous les passons à une fonction qui traite la commande.

function chat_command (cmd, arg) switch (cmd) case 'pseudo': var notice = pseudo + "a changé son nom en" + arg; nick = arg; socket.emit ('send', type: 'notice', message: notice); Pause; case 'msg': var to = arg.match (/ [a-z] + \ b /) [0]; var message = arg.substr (to.length, arg.length); socket.emit ('send', type: 'tell', message: message, to: to, from: nick); Pause; cas 'moi': var emote = nick + "" + arg; socket.emit ('send', type: 'emote', message: emote); Pause; default: console_out ("Ce n'est pas une commande valide."); 

Si l'utilisateur tape / Nick Gollum, la entaille la variable est réinitialisée pour être gollum, où il aurait pu être Smeagol avant et un avis est poussé sur le serveur.

Si l'utilisateur tape / msg bilbo Où est le précieux?, la même expression régulière est utilisée pour séparer le destinataire et le message, puis un objet avec le type de dire est poussé vers le serveur. Cela sera affiché un peu différemment d'un message normal et ne devrait pas être visible par les autres utilisateurs. Certes, notre serveur trop simple enverra aveuglément le message à tout le monde, mais le client ignorera les informations qui ne sont pas adressées au pseudo correct. Un serveur plus robuste pourrait être plus discret.

La commande emote est utilisée sous la forme de / moi mange un deuxième petit déjeuner. Le surnom est préfixé à l'emote d'une manière qui devrait être familière à quiconque a utilisé IRC ou joué à un jeu de rôle multijoueur, puis est poussé vers le serveur..

Le client: gestion des messages entrants

Maintenant, le client a besoin d'un moyen de recevoir des messages. Tout ce que nous avons à faire est d’accrocher le client Socket.io message événement et formater les données de manière appropriée pour la sortie.

socket.on ('message', fonction (data) var leader; if (data.type == 'chat' && data.nick! = nick) leader = couleur ("<"+data.nick+"> "," green "); console_out (leader + data.message); else if (data.type ==" notice ") console_out (color (data.message, 'cyan')); sinon if (data. type == "tell" && data.to == pseudo) leader = color ("[" + data.from + "->" data.to + "], rouge"), console_out (leader + data.message ); if (type de données == "emote") console_out (color (data.message, "cyan")););

Messages avec un type de bavarder cette n'étaient pas envoyés par le client en utilisant notre pseudo sont affichés avec le pseudo et le texte du chat. L'utilisateur peut déjà voir ce qu'il a tapé dans la ligne de lecture, il est donc inutile de la reproduire à nouveau. Ici j'utilise le ansi-couleur package pour colorer un peu la sortie. Ce n'est pas strictement nécessaire, mais cela rend le chat plus facile à suivre.

Messages avec un type de remarquer ou emote sont imprimés tels quels, bien que de couleur cyan.

Si le message est un dire et le surnom est égal au nom actuel de ce client, la sortie prend la forme de [Quelqu'un-> Vous] Salut!. Bien sûr, ce n'est pas terriblement privé. Si tu voulais voir tout le monde est messages, tout ce que vous avez à faire est de sortir le && data.to == nick partie. Idéalement, le serveur doit savoir à quel client envoyer le message et ne pas l'envoyer aux clients qui n'en ont pas besoin. Mais cela ajoute une complexité inutile qui dépasse le cadre de ce tutoriel..

Mets le feu!

Voyons maintenant si tout fonctionne. Pour le tester, lancez le serveur en lançant noeud serveur.js puis ouvrez deux nouvelles fenêtres de terminal. Dans les nouvelles fenêtres, lancez noeud client.js et entrez un surnom. Vous devriez alors pouvoir discuter entre eux, en supposant que tout se passe bien..

.