Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Blessed server (Node.js) over websocket to Xterm.js client in Browser

What I have:

  • Node.js script running Blessed, and http/websocket server.
  • Browser running Xterm.js and websocket client.

What I want to do:

  • Render blessed to the xterm window over websockets.

Server Code:

"use strict";

process.title = 'neosim-server';

var blessed = require('neo-blessed');
var contrib = require('blessed-contrib');
var webSocketServer = require('websocket').server;
var http = require('http');

const webSocketsServerPort = 8080;
var clients = [ ];

/**
 * HTTP server
 */
var server = http.createServer(function(request, response) {
    // Not important for us. We're writing WebSocket server,
    // not HTTP server
});
server.listen(webSocketsServerPort, function() {
    console.log((new Date()) + " Server is listening on port "
        + webSocketsServerPort + ".");
});

/**
 * WebSocket server
 */
var wsServer = new webSocketServer({
    // WebSocket server is tied to a HTTP server. WebSocket
    // request is just an enhanced HTTP request. For more info 
    // http://tools.ietf.org/html/rfc6455#page-6
    httpServer: server,
    autoAcceptConnections: false
});

function originIsAllowed(origin) {
    // put logic here to detect whether the specified origin is allowed.

    return true;
  }

// This callback function is called every time someone
// tries to connect to the WebSocket server
wsServer.on('request', function(request) {
    if (!originIsAllowed(request.origin)) {
        request.reject();
        console.log((new Date()) + ' Connection from origin ' + request.origin + ' rejected.');
        return;
    }
    console.log((new Date()) + ' Connection from origin '
        + request.origin + '.');

    // accept connection - you should check 'request.origin' to
    // make sure that client is connecting from your website
    // (http://en.wikipedia.org/wiki/Same_origin_policy)
    var connection = request.accept(null, request.origin); 
    // we need to know client index to remove them on 'close' event
    connection.write = connection.send;
    connection.read = connection.socket.read;
    connection.program = blessed.program({
        tput: true,
        input: connection,
        output: connection
    });
    connection.program.tput = blessed.tput({
        term: 'xterm-256color',
        extended: true,
    });
    connection.screen = blessed.screen({
        program: connection.program,
        tput: connection.program.tput,
        input: connection,
        output: connection,
        //smartCSR: true,
        terminal: 'xterm-256color',
        fullUnicode: true
    });


    var index = clients.push(connection) - 1;

    var userName = false;

    console.log((new Date()) + ' Connection accepted.');
    connection.program.write("test");

    // send back chat history
    /*if (history.length > 0) {
    connection.sendUTF(
        JSON.stringify({ type: 'history', data: history} ));
    }*/

    // terminal type message
    connection.on('term', function(terminal) {
        console.log("Term");
        connection.screen.terminal = terminal;
        connection.screen.render();
    });

    connection.on('resize', function(width, height) {
        console.log("Resize");
        connection.columns = width;
        connection.rows = height;
        connection.emit('resize');
    });

    // user sent some message
    connection.on('message', function(message) {
        if (message.type === 'utf8') { // accept only text
            console.log((new Date()) + ' Received Message: ' + message.utf8Data);
        }
    });

    // user disconnected
    connection.on('close', function(connection) {
        if (connection.screen && !connection.screen.destroyed) {
            connection.screen.destroy();
        }
    });

    connection.screen.key(['C-c', 'q'], function(ch, key) {
        connection.screen.destroy();
    });

    connection.screen.on('destroy', function() {
        if (connection.writable) {
            connection.destroy();
        }
    });

    connection.screen.data.main = blessed.box({
        parent: connection.screen,
        left: 'center',
        top: 'center',
        width: '80%',
        height: '90%',
        border: 'line',
        content: 'Welcome to my server. Here is your own private session.'
    });

    connection.screen.render();
});

Client page:

<!doctype html>
  <html lang="en">
    <head>
      <link rel="stylesheet" href="node_modules/xterm/dist/xterm.css" />
      <script src="node_modules/xterm/dist/xterm.js"></script>
      <script src="node_modules/xterm/dist/addons/attach/attach.js"></script>
      <script src="node_modules/xterm/dist/addons/fit/fit.js"></script>
      <script src="node_modules/xterm/dist/addons/winptyCompat/winptyCompat.js"></script>
    </head>
    <body>
      <div id="terminal"></div>
      <script>
        Terminal.applyAddon(attach);
        Terminal.applyAddon(fit);
        Terminal.applyAddon(winptyCompat);
        var term = new Terminal();
        var socket = new WebSocket('ws://localhost:8080', null);

        term.open(document.getElementById('terminal'));
        term.winptyCompatInit();
        term.fit();
        term.focus();
        term.write('Terminal ('+term.cols+'x'+term.rows+')\n\r');
//        term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')

//        window.addEventListener('resize', resizeScreen, false)

        function resizeScreen () {
            term.fit();
            socket.emit('resize', { cols: term.cols, rows: term.rows });
        };

        socket.onopen = function (connection) {
            term.attach(socket, true, false);
            //term.emit('term');
            //term.emit('resize');
        };

        socket.onmessage = function (message) {
            term.write(message);
        };
      </script>
    </body>
  </html>

The issue I am having is when screen.render() is called, nothing shows up on the client. When creating my blessed.screen, I've attempted using:

connection.screen = blessed.screen({input: connection.socket, output: connection.socket});

But this does not work either. In my current server code, I was attempting to trick blessed into using connection.send rather than connect.socket.write, because I think that browser websockets only accept the 'onmessage' event. Like so:

connection.write = connection.send;
connection.read = connection.socket.read;
connection.screen = blessed.screen({input: connection, output: connection});

Nothing I have tried so far has worked. What am I doing wrong here? Or is it simply that blessed won't work with browser websockets. Also, I know the connection is working, because I can send/receive messages on both ends.

like image 579
Sigma Reaver Avatar asked Oct 16 '18 20:10

Sigma Reaver


1 Answers

I got it working. I added the http responses to your server for serving the html/js files - just to get everything in one place. The main problem was the second parameter to your websocket connection in your client which is null. I just removed it, and then it was playing.

Also added an echo of your typing when pressing CR, so that you'll get a response from the server.

See modified sources below

screen capture of xterm in the browser

Server code:

process.title = 'neosim-server';

var blessed = require('neo-blessed');
var contrib = require('blessed-contrib');
var webSocketServer = require('websocket').server;
var http = require('http');
var fs = require('fs');

const webSocketsServerPort = 8080;
var clients = [ ];

/**
 * HTTP server
 */
var server = http.createServer(function(request, response) {
    // Not important for us. We're writing WebSocket server,
    // not HTTP server    

    let path = request.url.substring(1);
    if(path === '') {
        path = 'index.html';
    }

    if(fs.existsSync(path)) {
        if(path.indexOf('.js') === path.length-3) {
            response.setHeader('Content-Type', 'application/javascript');
        }
        response.end(fs.readFileSync(path));
    } else {
        response.statusCode = 404;
        response.end('');
    }
});

server.listen(webSocketsServerPort, function() {
    console.log((new Date()) + " Server is listening on port "
        + webSocketsServerPort + ".");
});

/**
 * WebSocket server
 */
var wsServer = new webSocketServer({
    // WebSocket server is tied to a HTTP server. WebSocket
    // request is just an enhanced HTTP request. For more info 
    // http://tools.ietf.org/html/rfc6455#page-6
    httpServer: server,
    autoAcceptConnections: false
});

function originIsAllowed(origin) {
    // put logic here to detect whether the specified origin is allowed.

    return true;
  }

// This callback function is called every time someone
// tries to connect to the WebSocket server
wsServer.on('request', function(request) {
    if (!originIsAllowed(request.origin)) {
        request.reject();
        console.log((new Date()) + ' Connection from origin ' + request.origin + ' rejected.');
        return;
    }
    console.log((new Date()) + ' Connection from origin '
        + request.origin + '.');

    // accept connection - you should check 'request.origin' to
    // make sure that client is connecting from your website
    // (http://en.wikipedia.org/wiki/Same_origin_policy)
    var connection = request.accept(null, request.origin); 
    // we need to know client index to remove them on 'close' event
    connection.write = connection.send;
    connection.read = connection.socket.read;
    connection.program = blessed.program({
        tput: true,
        input: connection,
        output: connection
    });
    connection.program.tput = blessed.tput({
        term: 'xterm-256color',
        extended: true,
    });
    connection.screen = blessed.screen({
        program: connection.program,
        tput: connection.program.tput,
        input: connection,
        output: connection,
        //smartCSR: true,
        terminal: 'xterm-256color',
        fullUnicode: true
    });


    var index = clients.push(connection) - 1;

    var userName = false;

    console.log((new Date()) + ' Connection accepted.');
    connection.program.write("test");

    // send back chat history
    /*if (history.length > 0) {
    connection.sendUTF(
        JSON.stringify({ type: 'history', data: history} ));
    }*/

    // terminal type message
    connection.on('term', function(terminal) {
        console.log("Term");
        connection.screen.terminal = terminal;
        connection.screen.render();
    });

    connection.on('resize', function(width, height) {
        console.log("Resize");
        connection.columns = width;
        connection.rows = height;
        connection.emit('resize');
    });

    let buf = '';
    // user sent some message
    connection.on('message', function(message) {
        if (message.type === 'utf8') { // accept only text
            console.log((new Date()) + ' Received Message: ' + message.utf8Data);
            buf += message.utf8Data;
            if(message.utf8Data === '\r') {
                console.log('You wrote:', buf);
                connection.sendUTF(buf +'\r\n');
                buf = '';
            }

        }
    });

    // user disconnected
    connection.on('close', function(connection) {
        if (connection.screen && !connection.screen.destroyed) {
            connection.screen.destroy();
        }
    });

    connection.screen.key(['C-c', 'q'], function(ch, key) {
        connection.screen.destroy();
    });

    connection.screen.on('destroy', function() {
        if (connection.writable) {
            connection.destroy();
        }
    });

    connection.screen.data.main = blessed.box({
        parent: connection.screen,
        left: 'center',
        top: 'center',
        width: '80%',
        height: '90%',
        border: 'line',
        content: 'Welcome to my server. Here is your own private session.'
    });

    connection.screen.render();
});

client code:

<!doctype html>
  <html lang="en">
    <head>
      <link rel="stylesheet" href="node_modules/xterm/dist/xterm.css" />
      <script src="node_modules/xterm/dist/xterm.js"></script>
      <script src="node_modules/xterm/dist/addons/attach/attach.js"></script>
      <script src="node_modules/xterm/dist/addons/fit/fit.js"></script>
      <script src="node_modules/xterm/dist/addons/winptyCompat/winptyCompat.js"></script>
    </head>
    <body>
      <div id="terminal"></div>
      <script>
        Terminal.applyAddon(attach);
        Terminal.applyAddon(fit);
        Terminal.applyAddon(winptyCompat);
        var term = new Terminal();
        var socket = new WebSocket('ws://localhost:8080');

        term.open(document.getElementById('terminal'));
        term.winptyCompatInit();
        term.fit();
        term.focus();
        term.write('Terminal ('+term.cols+'x'+term.rows+')\n\r');
//        term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')

//        window.addEventListener('resize', resizeScreen, false)

        function resizeScreen () {
            term.fit();
            socket.emit('resize', { cols: term.cols, rows: term.rows });
        };

        socket.onopen = function (connection) {
          console.log('connection opened');
            term.attach(socket, true, false);
            //term.emit('term');
            //term.emit('resize');
        };

        socket.onmessage = function (message) {
            term.write(message);
        };
      </script>
    </body>
  </html>
like image 157
Peter Salomonsen Avatar answered Nov 04 '22 18:11

Peter Salomonsen