Chat en tiempo real con Node.js 'Readline & Socket.io

Lo que vas a crear

Node.js tiene un módulo poco apreciado en su biblioteca estándar que es sorprendentemente útil. El módulo Readline hace lo que dice en la caja: lee una línea de entrada desde el terminal. Esto se puede usar para hacer una pregunta o dos al usuario, o para crear un mensaje en la parte inferior de la pantalla. En este tutorial, pretendo mostrar la capacidad de Readline y hacer una sala de chat de CLI en tiempo real respaldada por Socket.io. El cliente no solo enviará mensajes simples, sino que también tendrá comandos para emotes con /yo, mensajes privados con / msg, y permitir que los apodos se cambien con /mella.

Un poco sobre Readline

Este es probablemente el uso más simple de Readline:

var readline = require ('readline'); var rl = readline.createInterface (process.stdin, process.stdout); rl.question ("¿Cuál es tu nombre?", función (respuesta) console.log ("Hola", + respuesta); rl.close (););

Incluimos el módulo, creamos la interfaz Readline con los flujos de entrada y salida estándar, luego le hacemos una pregunta única al usuario. Este es el primer uso de Readline: hacer preguntas. Si necesita confirmar algo con un usuario, quizás en la forma del popular "¿Desea hacer esto? (S / n)", que impregna las herramientas de CLI, readline.question () es la manera de hacerlo.

La otra funcionalidad que proporciona Readline es la solicitud, que se puede personalizar desde su valor predeterminado ">"y temporalmente pausado para evitar la entrada. Para nuestro cliente de chat Readline, esta será nuestra interfaz principal. Habrá una sola aparición de readline.question () para pedirle al usuario un apodo, pero todo lo demás será readline.prompt ().

Manejando sus dependencias

Empecemos por la parte aburrida: las dependencias. Este proyecto hará uso de socket.io, la socket.io-cliente paquete y ansi-color. Tu paquetes.json El archivo debe verse algo como esto:

"name": "ReadlineChatExample", "version": "1.0.0", "description": "CLI chat con readline y socket.io", "author": "Matt Harzewski", "dependencies": "socket .io ":" latest "," socket.io-client ":" latest "," ansi-color ":" latest "," private ": true

correr npm instalar y deberias ser bueno para ir.

El servidor

Para este tutorial, usaremos un servidor Socket.io increíblemente simple. No hay nada más básico que esto:

var socketio = require ('socket.io'); // Escuche en el puerto 3636 var io = socketio.listen (3636); io.sockets.on ('conexión', función (socket) // Difunda el mensaje de un usuario a todos los demás en la sala socket.on ('enviar', función (datos) io.sockets.emit ('mensaje', datos);););

Todo lo que hace es tomar un mensaje entrante de un cliente y pasarlo a todos los demás. El servidor probablemente sería más robusto para una aplicación de mayor escala, pero para este simple ejemplo, debería ser suficiente.

Esto debe guardarse en el directorio del proyecto como server.js.

El Cliente: Incluye & Configuración

Antes de llegar a la parte divertida, debemos incluir nuestras dependencias, definir algunas variables e iniciar la interfaz Readline y la conexión de 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', puerto: 3636); var rl = readline.createInterface (process.stdin, process.stdout);

El código es bastante autoexplicativo en este punto. Tenemos nuestra variable de apodo, la conexión de socket (a través de la socket.io-cliente paquete) y nuestra interfaz Readline.

Socket.io se conectará a localhost a través del puerto 3636 en este ejemplo, por supuesto, esto se cambiaría al dominio y puerto de su propio servidor, si estuviera creando una aplicación de chat de producción. (¡No tiene mucho sentido chatear contigo mismo!)

El cliente: pidiendo el nombre del usuario

Ahora para nuestro primer uso de Readline! Queremos pedirle al usuario su elección de apodo, que los identificará en la sala de chat. Para ello, utilizaremos Readline's. pregunta() método.

// Establezca el nombre de usuario rl.question ("Introduzca un apodo:", función (nombre) nick = nombre; var msg = nick + "se ha unido al chat"; socket.emit ('enviar', tipo: ' aviso ', mensaje: msg); rl.prompt (true););

Establecimos la variable nick desde antes, al valor recopilado del usuario, enviamos un mensaje al servidor (que se transmitirá a los demás clientes) de que nuestro usuario se ha unido al chat y luego cambia la interfaz de Readline al modo de solicitud. los cierto valor pasado a rápido() asegura que el carácter de solicitud se muestra correctamente. (De lo contrario, el cursor puede moverse a la posición cero en la línea y la ">"no se mostrará.)

Desafortunadamente, Readline tiene un problema frustrante con el rápido() método. No juega bien con console.log (), que enviará el texto a la misma línea que el carácter de solicitud, dejando el camino ">"caracteres en todas partes y otras rarezas. Para remediar esto, no usaremos console.log En cualquier lugar de esta aplicación, guarda para un solo lugar. En su lugar, la salida se debe pasar a esta función:

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

Esta ligeramente La solución hacky garantiza que la línea actual en la consola esté vacía y que el cursor esté en la posición cero antes de imprimir la salida. A continuación, solicita explícitamente que se vuelva a emitir la solicitud, luego.

Así que para el resto de este tutorial, verá console_out () en lugar de console.log ().

El cliente: Manejo de entrada

Hay dos tipos de entrada que un usuario puede ingresar: chat y comandos. Sabemos que los comandos están precedidos por una barra, por lo que es fácil distinguir entre los dos.

Readline tiene varios manejadores de eventos, pero el más importante es, sin duda, línea. Siempre que se detecte un carácter de nueva línea en el flujo de entrada (desde la tecla de retorno o de ingreso), este evento se dispara. Así que tenemos que enganchar en línea para nuestro controlador de entrada.

rl.on ('línea', función (línea) si (línea [0] == "/" && line.length> 1) var cmd = line.match (/ [az] + \ b /) [0 ]; var arg = line.substr (cmd.length + 2, line.length); chat_command (cmd, arg); else // enviar el mensaje de chat socket.emit ('enviar', tipo: 'chat', mensaje: línea, nick: nick); rl.prompt (true););

Si el primer carácter de la línea de entrada es una barra diagonal, sabemos que es un comando, que requerirá más procesamiento. De lo contrario, solo enviamos un mensaje de chat regular y restablecemos el aviso. Observe la diferencia entre los datos enviados a través del socket aquí y para el mensaje de unión en el paso anterior. Está usando un diferente tipo, para que el cliente receptor sepa cómo formatear el mensaje y pasamos el mella variable también.

El nombre del comando (cmd) y el texto que sigue (arg) están aislados con un poco de expresión regular y magia de subcadena, luego los pasamos a una función que procesa el comando.

función chat_command (cmd, arg) switch (cmd) case 'nick': var notice = nick + "cambió su nombre a" + arg; nick = arg; socket.emit ('enviar', tipo: 'aviso', mensaje: aviso); descanso; caso 'msg': var a = arg.match (/ [a-z] + \ b /) [0]; mensaje var = arg.substr (to.length, arg.length); socket.emit ('send', type: 'tell', message: message, to: to, from: nick); descanso; caso 'me': var emote = nick + "" + arg; socket.emit ('enviar', tipo: 'emote', mensaje: emote); descanso; predeterminado: console_out ("Eso no es un comando válido"); 

Si el usuario escribe / nick gollum, la mella la variable se restablece para ser gollum, donde pudo haber sido smeagol Antes y se envía un aviso al servidor..

Si el usuario escribe / msg bilbo donde esta el precioso?, la misma expresión regular se usa para separar el destinatario y el mensaje, luego un objeto con el tipo de contar Se empuja al servidor. Esto se mostrará un poco diferente a un mensaje normal y no debería ser visible para otros usuarios. Es cierto que nuestro servidor demasiado simple enviará ciegamente el mensaje a todos, pero el cliente ignorará los avisos que no están dirigidos al apodo correcto. Un servidor más robusto podría ser más discreto..

El comando emote se utiliza en la forma de / yo estoy desayunando segundo. El apodo se añade al emote de una manera que debería ser familiar para cualquiera que haya usado IRC o haya jugado un juego de rol multijugador, luego se envía al servidor..

El cliente: manejo de mensajes entrantes

Ahora el cliente necesita una forma de recibir mensajes. Todo lo que necesitamos hacer es conectarnos al cliente de Socket.io mensaje evento y formato de los datos de manera adecuada para la salida.

socket.on ('mensaje', función (datos) var leader; if (data.type == 'chat' && data.nick! = nick) leader = color ("<"+data.nick+"> "," verde "); console_out (leader + data.message); else if (data.type ==" notice ") console_out (color (data.message, 'cyan')); else if (data. escriba == "decir" && data.to == nick) leader = color ("[" + data.from + "->" + data.to + "]", "red"); console_out (leader + data.message ); else if (data.type == "emote") console_out (color (data.message, "cyan")););

Mensajes con un tipo de charla ese no estaban enviados por el cliente usando nuestro apodo se muestran con el apodo y el texto de chat. El usuario ya puede ver lo que escribió en el Readline, por lo que no tiene sentido volver a emitirlo. Aquí estoy usando el ansi-color Paquete para colorear la salida un poco. No es estrictamente necesario, pero facilita el seguimiento del chat..

Mensajes con un tipo de darse cuenta o ser emocionado Se imprimen como están, aunque de color cian..

Si el mensaje es un contar y el apodo es igual al nombre actual de este cliente, la salida toma la forma de [Alguien-> Tú] Hola!. Por supuesto, esto no es terriblemente privado. Si quisieras ver todo el mundo es mensajes, todo lo que tendría que hacer es sacar la && data.to == nick parte. Idealmente, el servidor debería saber a qué cliente enviar el mensaje y no enviarlo a los clientes que no lo necesitan. Pero eso agrega una complejidad innecesaria que está más allá del alcance de este tutorial..

Préndelo!

Ahora veamos si todo funciona. Para probarlo, ejecute el servidor ejecutando nodo server.js y luego abrir un par de nuevas ventanas de terminal. En las nuevas ventanas, corre. nodo client.js y escriba un apodo. Entonces deberías poder chatear entre ellos, asumiendo que todo va bien.

.