'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 // * renderers // * port // * frequency // * modulation // // 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;