]> git.r.bdr.sh - rbdr/tomato-sauce/blob - lib/tomato_sauce.js
Merge branch 'feature/the-actual-tomato-sauce' into develop
[rbdr/tomato-sauce] / lib / tomato_sauce.js
1 'use strict';
2
3 const Net = require('net');
4 const EventEmitter = require('events');
5
6 const Util = require('./util');
7
8 // Interpret as Command Sequences.
9 const kEscape = new Buffer([0xFF, 0xF4, 0XFF, 0xFD, 0x06]); // IAC IP IAC DO TIMING_MARK
10 const kNAWSRequest = new Buffer([0xFF, 0xFD, 0X1F]); // IAC DO NAWS
11 const kNAWSResponse = new Buffer([0xFF, 0xFB, 0X1F, 0xFF, 0xFA, 0X1F]); // IAC WILL NAWS IAC SB NAWS
12
13 // Main tomato sauce class. Properties:
14 // * screens <TomatoSauce.IScreen[]>
15 // * renderers <TomatoSauce.IRenderer[]>
16 // * port <int>
17 // * frequency <int>
18 // * modulation <int>
19 //
20 // The main entry point is the #run() function.
21 //
22 // It emits a listening event that contains the server information on
23 // the `server` key inside the `data` property of the event.
24 //
25 // It also emits an error event that contains the error information on
26 // the `error` key inside the `data` property of the event.
27 const TomatoSauce = class TomatoSauce extends EventEmitter {
28
29 constructor (config) {
30 super();
31
32 this.screens = [];
33 this.renderers = [];
34
35 Object.assign(this, config || {});
36 }
37
38 // Here's where things get started.
39 run () {
40 this._startServer();
41 this._bindEvents();
42 }
43
44 // Creates a socket, server based on the configuration. Emits the
45 // listening event when ready.
46 _startServer () {
47 const server = Net.createServer();
48 this.server = server;
49 server.listen(this.port, function () {
50 this.emit('listening', {
51 data: {
52 server: server
53 }
54 });
55 }.bind(this));
56 }
57
58 _bindEvents () {
59 // Send the error event all the way up.
60 this.server.on('error', function (err) {
61 this.emit('error', {
62 data: {
63 error: err
64 }
65 });
66 }.bind(this));
67
68 // Send the error event all the way up.
69 this.server.on('connection', function (socket) {
70 this._renderScreen(socket);
71 }.bind(this));
72 }
73
74 // Obtains viewport size, and initializes a random screen with a random
75 // renderer, setting the required interval to draw.
76 _renderScreen (socket) {
77 let connectionData = {
78 width: null,
79 height: null,
80 modulation: 0,
81 screen: this._getScreen(),
82 renderer: this._getRenderer(),
83 socket: socket
84 };
85 let interval = null;
86
87 socket.write(kNAWSRequest);
88
89 socket.on('data', function (data) {
90 if (data.slice(0, 6).compare(kNAWSResponse) === 0) {
91 connectionData.width = Util.parse16BitBuffer(data.slice(6, 8));
92 connectionData.height = Util.parse16BitBuffer(data.slice(8, 10));
93
94 socket.write('\x1B[2J'); // Clear the Screen (CSI 2 J)
95 interval = setInterval(this._writeMessage.bind(this, connectionData), this.frequency);
96 }
97
98 if (data.compare(kEscape) === 0) {
99 socket.write('\n');
100 clearInterval(interval);
101 socket.end();
102 }
103 }.bind(this));
104 }
105
106 // Resets the cursor, gets a frame and sends it to the socket.
107 _writeMessage (connectionData) {
108 let payload = connectionData.screen(connectionData.modulation, connectionData.width, connectionData.height, connectionData.renderer);
109 let message = `\x1B[1;1H${payload}`; // reset cursor position before payload
110
111 connectionData.modulation = (connectionData.modulation + this.modulation) % 256;
112 connectionData.socket.write(message);
113 }
114
115 _getScreen () {
116 return Util.pickRandom(this.screens);
117 }
118
119 _getRenderer () {
120 return Util.pickRandom(this.renderers);
121 }
122 };
123
124 // Defaults.
125 TomatoSauce.prototype.port = 9999;
126 TomatoSauce.prototype.frequency = 333;
127 TomatoSauce.prototype.modulation = 5;
128
129 module.exports = TomatoSauce;