aboutsummaryrefslogtreecommitdiff
path: root/lib/tomato_sauce.js
blob: 0ff087a7d6f2454072897eccb4ac8298bf1b8372 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
'use strict';

const Net = require('net');
const EventEmitter = require('events');

const Util = require('./util');

// Interpret as Command Sequences.
const kEscape = new Buffer([0xFF, 0xF4, 0XFF, 0xFD, 0x06]); // IAC IP IAC DO TIMING_MARK
const kNAWSRequest = new Buffer([0xFF, 0xFD, 0X1F]); // IAC DO NAWS
const kNAWSResponse = new Buffer([0xFF, 0xFB, 0X1F, 0xFF, 0xFA, 0X1F]); // IAC WILL NAWS IAC SB NAWS

// Main tomato sauce class. Properties:
// * screens <TomatoSauce.IScreen[]>
// * renderers <TomatoSauce.IRenderer[]>
// * port <int>
// * frequency <int>
// * modulation <int>
//
// The main entry point is the #run() function.
//
// It emits a listening event that contains the server information on
// the `server` key inside the `data` property of the event.
//
// It also emits an error event that contains the error information on
// the `error` key inside the `data` property of the event.
const TomatoSauce = class TomatoSauce extends EventEmitter {

  constructor (config) {
    super();

    this.screens = [];
    this.renderers = [];

    Object.assign(this, config || {});
  }

  // Here's where things get started.
  run () {
    this._startServer();
    this._bindEvents();
  }

  // Creates a socket, server based on the configuration. Emits the
  // listening event when ready.
  _startServer () {
    const server = Net.createServer();
    this.server = server;
    server.listen(this.port, function () {
      this.emit('listening', {
        data: {
          server: server
        }
      });
    }.bind(this));
  }

  _bindEvents () {
    // Send the error event all the way up.
    this.server.on('error', function (err) {
      this.emit('error', {
        data: {
          error: err
        }
      });
    }.bind(this));

    // Send the error event all the way up.
    this.server.on('connection', function (socket) {
      this._renderScreen(socket);
    }.bind(this));
  }

  // Obtains viewport size, and initializes a random screen with a random
  // renderer, setting the required interval to draw.
  _renderScreen (socket) {
    let connectionData = {
      width: null,
      height: null,
      modulation: 0,
      screen: this._getScreen(),
      renderer: this._getRenderer(),
      socket: socket
    };
    let interval = null;

    socket.write(kNAWSRequest);

    socket.on('data', function (data) {
      if (data.slice(0, 6).compare(kNAWSResponse) === 0) {
        connectionData.width = Util.parse16BitBuffer(data.slice(6, 8));
        connectionData.height = Util.parse16BitBuffer(data.slice(8, 10));

        socket.write('\x1B[2J'); // Clear the Screen (CSI 2 J)
        interval = setInterval(this._writeMessage.bind(this, connectionData), this.frequency);
      }

      if (data.compare(kEscape) === 0) {
        socket.write('\n');
        clearInterval(interval);
        socket.end();
      }
    }.bind(this));
  }

  // Resets the cursor, gets a frame and sends it to the socket.
  _writeMessage (connectionData) {
    let payload = connectionData.screen(connectionData.modulation, connectionData.width, connectionData.height, connectionData.renderer);
    let message = `\x1B[1;1H${payload}`; // reset cursor position before payload

    connectionData.modulation = (connectionData.modulation + this.modulation) % 256;
    connectionData.socket.write(message);
  }

  _getScreen () {
    return Util.pickRandom(this.screens);
  }

  _getRenderer () {
    return Util.pickRandom(this.renderers);
  }
};

// Defaults.
TomatoSauce.prototype.port = 9999;
TomatoSauce.prototype.frequency = 333;
TomatoSauce.prototype.modulation = 5;

module.exports = TomatoSauce;