Realtidschatt med Node.js 'Readline & Socket.io

Vad du ska skapa

Node.js har en undervärderad modul i sitt standardbibliotek som är överraskande användbart. Readline-modulen gör vad som står på rutan: den läser en linje av ingång från terminalen. Detta kan användas för att fråga användaren en fråga eller två, eller för att skapa en prompt längst ner på skärmen. I denna handledning tänker jag visa upp möjligheten hos Readline och skapa ett realtid CLI-chattrum som stöds av Socket.io. Klienten kommer inte bara att skicka enkla meddelanden, men har kommandon för emotes med /mig, privata meddelanden med / msg, och tillåta att smeknamn ändras med /hack.

Lite om Readline

Det här är förmodligen det enklaste användandet av Readline:

var readline = kräver ("readline"); var rl = readline.createInterface (process.stdin, process.stdout); rl.question ("Vad heter du?", funktion (svar) console.log ("Hej," + svar); rl.close (););

Vi inkluderar modulen, skapa Readline-gränssnittet med standardinmatnings- och utmatningsflödena och fråga användaren en engångsfråga. Det här är den första användningen av Readline: ställer frågor. Om du behöver bekräfta någonting med en användare, kanske i form av det ständigt populära, "Vill du göra det här? (Y / n)", som genomtränger CLI-verktyg, readline.question () är sättet att göra det.

Den andra funktionaliteten som Readline tillhandahåller är snabb, vilken kan anpassas från standardinställningen ">"karaktär och tillfälligt pausad för att förhindra inmatning. För vår Readline-chatklient kommer detta att vara vårt primära gränssnitt. Det kommer att finnas en enda förekomst av readline.question () att fråga användaren för ett smeknamn, men allt annat kommer att bli readline.prompt ().

Hantera dina beroende

Låt oss börja med den tråkiga delen: beroenden. Detta projekt kommer att utnyttja socket.io, de socket.io-klient paket och ANSI-färg. Din packages.json filen ska se ut så här:

"namn": "ReadlineChatExample", "version": "1.0.0", "beskrivning": "CLI chatta med readline och socket.io", "author": "Matt Harzewski", "beroenden" .io ":" senaste "," socket.io-client ":" senaste "," ansi-color ":" senaste "," privata ": true

Springa npm installera och du borde vara bra att gå.

Servern

För denna handledning använder vi en otroligt enkel Socket.io-server. Det blir inte mer grundläggande än detta:

var socketio = kräver ('socket.io'); // Lyssna på port 3636 var io = socketio.listen (3636); io.sockets.on ("anslutning", funktion (uttag) // Sända en användares meddelande till alla andra i rummet socket.on ("send", funktion (data) io.sockets.emit ("message" data);););

Allt det gör är att ta emot ett inkommande meddelande från en klient och vidarebefordra det till alla andra. Servern skulle förmodligen vara mer robust för en större applikation, men för det här enkla exemplet borde det vara tillräckligt.

Detta bör sparas i projektkatalogen som server.js.

Klienten: Inkluderar & Setup

Innan vi kommer till den roliga delen måste vi inkludera våra beroenden, definiera vissa variabler och starta Readline-gränssnittet och socketanslutningen.

var readline = kräver ('readline'), socketio = kräver ('socket.io-client'), util = kräver ('util'), färg = kräver ("ansi-färg"). var nick; var socket = socketio.connect ("localhost", port: 3636); var rl = readline.createInterface (process.stdin, process.stdout);

Koden är ganska självförklarande vid denna punkt. Vi har vår smeknamn variabel, uttaget (via socket.io-klient paket) och vårt Readline-gränssnitt.

Socket.io kommer att ansluta till localhost över porten 3636 I det här exemplet skulle det naturligtvis ändras till din egen servers domän och port, om du skapade en produktionsklippapp. (Det är inte så mycket att chatta med dig själv!)

Klienten: Be om användarens namn

Nu för vår första användning av Readline! Vi vill fråga användaren för sitt val av smeknamn, vilket kommer att identifiera dem i chattrummet. Därför använder vi Readlines fråga() metod.

// Ange användarnamnet rl.question ("Vänligen ange ett smeknamn:", funktion (namn) nick = namn; var msg = nick + "har gått med i chatten"; socket.emit ("send", typ: meddelande ", meddelande: msg); rl.prompt (true););

Vi ställer in nickvariabeln från tidigare, till det värde som samlats in från användaren, skicka ett meddelande till servern (som kommer att vidarebefordras till de andra klienterna) att vår användare har anslutit sig till chatten och sedan byta läsgränssnittet till snabbläget. De Sann värdet passerat till prompt() ser till att den snabba karaktären visas korrekt. (Annars kan markören flytta till nollpositionen på linjen och ">"kommer inte att visas.)

Tyvärr har Readline ett frustrerande problem med prompt() metod. Det spelar inte bra med console.log (), som kommer att skriva ut text på samma rad som den snabba karaktären, vilket lämnar ">"tecken överallt och annan konstighet. För att avhjälpa detta kommer vi inte att använda console.log var som helst i den här applikationen, spara för en plats. Istället bör produktionen överföras till denna funktion:

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

Detta lite hacky-lösningen säkerställer att den nuvarande raden i konsolen är tom, och att markören är i nollposition innan utskrift skrivs ut. Då begär det uttryckligen att prompten ska matas ut igen, efteråt.

Så för resten av denna handledning kommer du att se console_out () istället för console.log ().

Klienten: Hantering av ingång

Det finns två typer av inmatningar som en användare kan ange: chatt och kommandon. Vi vet att kommandon föregås av ett snedstreck, så det är lätt att skilja mellan de två.

Readline har flera händelsehanterare, men det viktigaste är utan tvekan linje. När en newline-karaktär detekteras i ingångsströmmen (från retur- eller enter-tangenten), brinner den här händelsen. Så vi behöver koppla in linje för vår ingångshanterare.

rl.on ('line', funktion (rad) if (line [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 // skicka chattmeddelande socket.emit ('send', typ: 'chat' meddelande: linje, nick: nick); rl.prompt (true););

Om den första bokstaven på inmatningsraden är en snedstreck vet vi att det är ett kommando, vilket kräver mer behandling. Annars skickar vi ett vanligt chattmeddelande och återställer prompten. Observera skillnaden mellan de data som skickas över hylsan här och för anslutningsmeddelandet i föregående steg. Det använder en annan typ, så den mottagande klienten vet hur man formaterar meddelandet och vi passerar hack variabel också.

Kommandonamnet (cmd) och texten som följer (arg) isoleras med lite regex och substring magi, så skickar vi dem till en funktion som behandlar kommandot.

funktion chat_command (cmd, arg) switch (cmd) case 'nick': var notice = nick + "ändrade sitt namn till" + arg; nick = arg; socket.emit ('send', typ: 'notice', meddelande: notice); ha sönder; fall 'msg': var till = arg.match (/ [a-z] + \ b /) [0]; var message = arg.substr (to.length, arg.length); socket.emit ('send', typ: 'tell', meddelande: meddelande, till: till, från: nick); ha sönder; fallet "mig": var emote = nick + "" + arg; socket.emit ('send', typ: 'emote', meddelande: emote); ha sönder; standard: console_out ("Det är inte ett giltigt kommando."); 

Om användaren skriver / nick gollum, de hack variabeln är återställd för att vara Gollum, var det kunde ha varit Smeagol innan och ett meddelande trycks till servern.

Om användaren skriver / msg bilbo Var är den värdefulla?, samma regex används för att skilja mottagaren och meddelandet, sedan ett objekt med typen av säga drivs till servern. Detta kommer att visas lite annorlunda än ett normalt meddelande och ska inte vara synligt för andra användare. Visserligen kommer vår alltför enkla server att blinda trycka meddelandet ut till alla, men klienten ignorerar berättar att de inte riktas till rätt smeknamn. En mer robust server kan vara mer diskret.

Emote-kommandot används i form av / jag äter andra frukost. Smeknamnet är prepended till emote på ett sätt som borde vara bekant för alla som har använt IRC eller spelat ett rollspel som spelar flera spelare, då trycks den på servern.

Klienten: Hantering inkommande meddelanden

Nu behöver kunden ett sätt att ta emot meddelanden. Allt vi behöver göra är att haka i Socket.io-klientens meddelande händelse och formatera data på lämpligt sätt för utmatning.

socket.on ("message", funktion (data) var ledare; om (data.type == 'chat' && data.nick! = nick) leader = color<"+data.nick+"> "," grön "); console_out (ledare + data.message); annars om (data.type ==" notice ") console_out (färg (data.message, cyan)); annars om skriv == "berätta" && data.to == nick) leader = color ("[" + data.from + "->" + data.to + "]", "red"); console_out (ledare + data.message ); annars om (data.type == "emote") console_out (färg (data.message, "cyan")););

Meddelanden med en typ av chatt den där inte skickas av kunden med vårt smeknamn visas med smeknamn och chatttext. Användaren kan redan se vad de skrev in i läslinjen, så det finns ingen anledning att skriva ut det igen. Här använder jag ANSI-färg paketet för att färga produktionen lite. Det är inte absolut nödvändigt, men det gör det lättare att följa chatten.

Meddelanden med en typ av lägga märke till eller emote är tryckta som-är, men färgad cyan.

Om meddelandet är a säga och smeknamnet är lika med den här klientens nuvarande namn, utmatningen har formen av [Någon-> Du] Hej!. Det här är självklart inte hemskt privat. Om du ville se allas Meddelanden, allt du behöver göra är att ta ut && data.to == nick del. Idealt sett bör servern veta vilken klient som ska driva meddelandet och inte skicka det till kunder som inte behöver det. Men det lägger till onödig komplexitet som ligger utanför omfattningen av denna handledning.

Starta den!

Nu ska vi se om allt fungerar. För att testa det, starta servern genom att köra nod server.js och öppna sedan ett par nya terminalfönster. I de nya Windows, springa nod client.js och ange ett smeknamn. Du ska då kunna chatta mellan dem, förutsatt att allt går bra.

.