Ssh-chat

от автора

Привет, Хабр. Console chat отличная вещь, но для фронтендеров, а что если вы хотите такой же, но для бэкэнда. Если да, то эта статья для вас. Но какой инструмент часто используют в бэкенде? Правильно ssh, так что представляю sshchat

Как это будет выглядеть

Где-то на сервере крутится программа на ноде.
Как только кто-то хочет, подключится к чату он вводит:

ssh server -p 8022

После этого система спрашивает пароль и сверяет его с паролем в специальном файле. Если пароль совпал, то подключаем к чату(юзер получает 100 предыдущих сообщений и все остальные видят что он подключился)
Дальше он принимает сообщения других, и может написать своё.
Вот с сообщениями поинтереснее:

@box{@color(red){Red text in box}}

Отправит красный текст в коробке.

Приступим

Для работы с ssh мы будем использовать https://www.npmjs.com/package/ssh2
Для форматирования используем chalk и boxen
Так что установим их

npm i ssh2 chalk boxen

Теперь сам код одна из самых важных частей это парсер сообщений:
https://github.com/maximmasterr/ssh-server/blob/master/parserExec.js

// Подключаем chalk и boxen const chalk = require('chalk'); const boxen = require('boxen');  // Здесь прописаны методы которые мы сможем использовать через @ // Функции принимают 2 аргумента то что в скобках и текс в фигурных скобках let methods = {   color: function(args, text) {     return chalk.keyword(args)(text);   },    bold: function(args, text) {     return chalk.bold(text);   },    underline: function(args, text) {     return chalk.underline(text);   },    hex: function(args, text) {     return chalk.hex(args)(text);   },    box: function(args, text) {     return boxen(text, {       borderStyle: 'round',       padding: 1,       borderColor: 'blueBright'     });   } };  // Сам парсер  function parseAndExecute(str) {   let pos = 0;   let stage = 0;   let nS = '';   let bufs = ['', '', '', ''];   let level = 0;    while (pos < str.length) {     let symbol = str[pos];     pos++;      if (symbol == '\\' && '(){}@'.indexOf(str[pos]) !== -1) {       bufs[stage] += str[pos];       pos++;       continue;     }      if (stage == 0 && symbol == '@') {       stage++;       nS += bufs[0];       bufs[0] = '';       continue;     } else if (stage >= 1) {       if (symbol == '(')         if (stage < 2) {           stage = 2;         } else {           level++;         }        if (symbol == ')' && stage >= 2 && level > 0) level--;        if (symbol == '{')         if (stage != 3) {           stage = 3;         } else {           level++;         }        if (symbol == '}') {         if (level == 0) {           bufs[3] += '}';            nS += methods[bufs[1]](bufs[2].slice(1, -1), parseAndExecute(bufs[3].slice(1, -1)));            bufs = ['', '', '', ''];           stage = 0;           continue;         } else {           level--;         }       }     }     bufs[stage] += symbol;   }   return nS + bufs[0]; }  module.exports.parseAndExecute = parseAndExecute;

Форматирование:
https://github.com/maximmasterr/ssh-server/blob/master/format.js

const chalk = require('chalk'); const { parseAndExecute } = require('./parserExec')  // Стилизуем ник(Генерируем цвет и делаем жирным) function getNick(nick) {   let hash = 0;   for (var i = 0; i < nick.length; i++) hash += nick.charCodeAt(i) - 32;    return chalk.hsv((hash + 160) % 360, 90, 90)(chalk.bold(nick)); }  module.exports.format = function(nick, message) {   const nickSpace = '\r  ' + ' '.repeat(nick.length);   nick = getNick(nick) + ': ';    message = message.replace(/\\n/gm, '\n'); // Заменяем \n новыми строками   message = parseAndExecute(message) // Парсим    // Добавлям к каждой новой строке отступ   message = message     .split('\n')     .map((e, i) => '' + (i !== 0 ? nickSpace : '') + e)     .join('\n');    return nick + message; };

Методы для отправки сообщения всем пользователям и сохранения 100 сообщений:
https://github.com/maximmasterr/ssh-server/blob/master/broadcaster.js

let listeners = []; // Все пользователи let cache = new Array(100).fill('') // Кэш   // Добавления и удаление подписчиков module.exports.addListener = write => listeners.push(write) - 1; module.exports.delListener = id => listeners.splice(id, 1);  // Отправляем сообщение module.exports.broadcast = msg => {    cache.shift()   cache.push(msg)   process.stdout.write(msg)   listeners.forEach(wr => wr(msg)); }  // Получаем кэш module.exports.getCache = ()=>cache.join('\r\033[1K')

Лобби, создание сервера и авторизация
https://github.com/maximmasterr/ssh-server/blob/master/lobby.js

const { Server } = require('ssh2'); const { readFileSync } = require('fs');  const hostKey = readFileSync('./ssh'); // Читаем ключ const users = JSON.parse(readFileSync('./users.json')); // Юзеры  let connectionCallback = () => {};  module.exports.createServer = function createServer({ lobby }) {   // Создаём сервер   const server = new Server(     {       banner: lobby, // Баннер встречает до ввода пароля       hostKeys: [hostKey]     },     function(client) {       nick = '';       client         .on('authentication', ctx => {  // Авторизация           if (ctx.method !== 'password') return ctx.reject();           if (ctx.password !== users[ctx.username]) ctx.reject();           nick = ctx.username;           ctx.accept();         })         .on('ready', function() {           connectionCallback(client, nick);         });     }   );    return server };  module.exports.setConnectCallback = callback => { // Устанавливает колбэк при подключении   connectionCallback = callback; };

Различные методы:
https://github.com/maximmasterr/ssh-server/blob/master/utils.js

const { createInterface } = require('readline');  module.exports.getStream = function(client, onStream, onEnd){   client  // Получает стрим и клиента     .on('session', function(accept, reject) {       accept()         .on('pty', accept => accept & accept())         .on('shell', accept => onStream(accept()));     })     .on('end', () => onEnd()); }  // Создаём коммуникатор  module.exports.getCommunicator = function(stream, onMessage, onEnd){    let readline = createInterface({ // Интерфейс для считывания строк     input: stream,     output: stream,     prompt: '> ',     historySize: 0,     terminal: true   })   readline.prompt()    readline.on('close', ()=>{     radline = null;     onEnd()     stream.end()   })    readline.on('line', (msg)=>{     stream.write('\033[s\033[1A\033[1K\r')     onMessage(msg)     readline.prompt()   })    // Метод для записи сообщения   return msg=>{     stream.write('\033[1K\r' + msg)     readline.prompt()   } }

А теперь объединим
https://github.com/maximmasterr/ssh-server/blob/master/index.js

const { createServer, setConnectCallback } = require('./lobby'); const { getStream, getCommunicator } = require('./utils'); const { addListener, delListener, broadcast, getCache } = require('./broadcaster'); const { format, getNick } = require('./format');  //  Функция создания сервера  module.exports = function({ lobby = 'Hi' } = {}) {   const server = createServer({     lobby   });    setConnectCallback((client, nick) => { // Ожидание соединения     console.log('Client authenticated!');     let id = null;     getStream( // Получаем стрим       client,       stream => {         const write = getCommunicator( // И интерфейс           stream,           msg => {             if (msg == '') return;             try {               broadcast(format(nick, msg) + '\n'); // Как только получим сообщение, отправим его всем             } catch (e) {}           },           () => {}         );          id = addListener(write); // Слушаем сообщения         write('\033c' + getCache()); // Отправляем кэш         broadcast(getNick(nick) + ' connected\n'); // Сообщаем о подключении       },       () => {          delListener(id);         broadcast(getNick(nick) + ' disconnected\n') // Сообщаем об отключении       }     );   });    server.listen(8022); };

И финальный этап пример сервера:

const chat = require('.')  chat({})

Так же в файле users.json описаны юзеры и их пароли

Выводы

Вот так можно написать не самый простой чат в ssh
Для такого чата не нужно писать клиент, он обладает возможностями оформления, и его может развернуть любой желающий
Что ещё можно сделать:

  • Добавить возможность создания своих функций оформления
  • Добавить поддержку markdown
  • Добавить поддержку ботов
  • Отправка файлов по scp

    Финальный репозиторий: https://github.com/maximmasterr/ssh-server


ссылка на оригинал статьи https://habr.com/ru/post/466633/