Skip to content

Instantly share code, notes, and snippets.

@alenaksu
Last active January 18, 2019 17:12
Show Gist options
  • Save alenaksu/d32b50b888e5b1f908a3700519b12949 to your computer and use it in GitHub Desktop.
Save alenaksu/d32b50b888e5b1f908a3700519b12949 to your computer and use it in GitHub Desktop.
Node.js Telnet Chat Server
{
"server": {
"maxConnections": 100,
"host": "0.0.0.0",
"port": 10000
},
"chat": {
"messageLength": 150,
"messages": {
"welcome": "Welcome to chat server. Press CTRL+C to leave.",
"nickname": "Enter a nickname: ",
"logout": "%s has left the chat",
"login": "%s has joined the chat"
},
"serverNickname": "server"
}
}
const net = require('net');
const { format } = require('util');
const telnet = require('./telnet');
const Config = require('./config.json');
const Messages = Config.chat.messages;
const clients = [];
const messageListener = fn => data => {
console.log('Received data', data, data.toString());
data = data
.toString()
.trim()
.replace(/[^\w\d \t]/g, '')
.substring(0, Config.chat.messageLength);
data && fn(data);
};
const exitHandler = socket => {
return data => telnet.isInterrput(data) && socket.end('Bye bye.');
};
const broadcastHandler = (socket, nickname) =>
messageListener(message => sendBroadcast(socket, nickname, message));
const endHandler = (socket, nickname) => () => {
clients.splice(clients.indexOf(socket), 1);
sendBroadcast(null, null, format(Messages.logout, nickname));
console.log(`${socket.remoteAddress} disconnected`);
};
function sendMessageLn(socket, from, message) {
sendMessage(socket, from, message + '\r\n');
}
function sendMessage(socket, from, message) {
if (from) message = `<${from}> ${message}`;
socket.write(message);
}
function sendBroadcast(socket, from, message) {
if (!message) return;
clients.forEach(client => {
telnet.sendEraseLine(client);
if (client !== socket) sendMessageLn(client, from, message);
});
}
function askNickname(socket) {
return new Promise((resolve, reject) => {
const ask = () => {
socket.once('data', onData);
sendMessage(socket, null, Messages.nickname);
};
const onData = messageListener(data => {
if (!data.length) return ask();
resolve(data);
});
ask();
});
}
function createServer() {
const server = net.createServer(async socket => {
try {
console.log(`Connection from ${socket.remoteAddress}`);
sendMessageLn(socket, null, Messages.welcome);
socket.on('data', exitHandler(socket));
const nickname = await askNickname(socket);
clients.push(socket);
socket.on('data', broadcastHandler(socket, nickname));
// telnet.sendCommand(socket, telnet.Commands.NOP);
// telnet.sendCommand(socket, telnet.Commands.WILL, telnet.Options.ECH);
// telnet.sendCommand(socket, telnet.Commands.WILL, telnet.Options.SGA);
sendBroadcast(null, null, format(Messages.login, nickname));
console.log(
`${socket.remoteAddress} joined the chat as ${nickname}`
);
} catch (e) {
console.log('error', e);
socket.end();
}
});
server.maxConnections = Config.server.maxConnections;
server.on('close', () => {
console.log(`Server closed.`);
});
return server;
}
if (require.main === module) {
createServer().listen(Config.server.port);
console.log(`Server listening on port ${Config.server.port}`);
}
module.exports = {
createServer
};
const Commands = {
IAC: 0xff,
SE: 0xf0,
NOP: 0xf1,
DataMark: 0xf2,
BRK: 0xf3,
IP: 0xf4,
AO: 0xf5,
AYT: 0xf6,
EC: 0xf7,
EL: 0xf8,
GA: 0xf9,
SB: 0xfa,
WILL: 0xfb,
WONT: 0xfc,
DO: 0xfd,
DONT: 0xfe
};
const Options = {
ECH: 0x1,
SGA: 0x3,
STS: 0x5,
TimingMark: 0x6,
TerminalType: 0x18,
WindowSize: 0x1f,
TerminalSpeed: 0x20,
RFC: 0x21,
LineMode: 0x22,
EnvVars: 0x24
}
const isCommand = buffer => buffer[0] === Commands.IAC;
const isInterrput = buffer => isCommand(buffer) && buffer[1] === Commands.IP;
const sendCommand = (socket, command, option) =>
socket.write(
Uint8Array.from(
[Commands.IAC, command, option].filter(b => b !== undefined)
)
);
const sendEraseLine = socket => sendCommand(socket, Commands.EL);
module.exports = {
isInterrput,
Commands,
sendCommand,
sendEraseLine,
Options
};
import test from 'ava';
import Telnet from 'telnet-client';
import Config from '../src/chat-server/config.json';
import chatServer from '../src/chat-server';
const connectionParams = {
host: '127.0.0.1',
port: Config.server.port,
shellPrompt: '/ # ',
ors: '\r\n',
timeout: 1500,
negotiationMandatory: false,
debug: true,
initialLFCR: true
};
let server;
test.before(() => {
server = chatServer.createServer();
server.listen(Config.server.port);
});
test.after.always(() => {
server.close();
});
test(`should listens on port ${Config.server.port}`, async t => {
const connection = new Telnet();
await connection.connect(connectionParams);
connection.end();
t.pass();
});
test.cb(`should ask for nickname`, t => {
const connection = new Telnet();
connection.on('data', buffer => {
console.log(buffer);
~buffer.toString().indexOf(Config.chat.messages.nickname)
? t.pass()
: t.fail();
connection.end();
t.end();
});
connection.connect(connectionParams).then(() => connection.send(''));
});
test(`should broadcast messages to all clients`, t => {
return new Promise(async (resolve, reject) => {
const secret = Math.random()
.toString(36)
.slice(2);
const alice = new Telnet(),
bob = new Telnet();
await alice.connect(connectionParams);
await alice.send('alice');
await bob.connect(connectionParams);
await bob.send('bob');
alice.on('data', buffer => {
t.true(buffer.toString().indexOf(secret) !== -1);
resolve();
});
bob.send(secret);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment