X-Git-Url: https://git.r.bdr.sh/rbdr/tomato-sauce/blobdiff_plain/c7b4bd19a006d61c3b4979e884370d123cec7524..a0666be3ab58ed83ad6d622cfe2b8293c40dffbb:/lib/tomato_sauce.js?ds=sidebyside diff --git a/lib/tomato_sauce.js b/lib/tomato_sauce.js index 0ff087a..e71a207 100644 --- a/lib/tomato_sauce.js +++ b/lib/tomato_sauce.js @@ -5,125 +5,185 @@ 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 internals = { + // Interpret as Command Sequences. + kEscape: Buffer.from([0xFF, 0xF4, 0XFF, 0xFD, 0x06]), // IAC IP IAC DO TIMING_MARK + kNAWSRequest: Buffer.from([0xFF, 0xFD, 0X1F]), // IAC DO NAWS + kNAWSResponse: Buffer.from([0xFF, 0xFB, 0X1F, 0xFF, 0xFA, 0X1F]) // IAC WILL NAWS IAC SB NAWS +}; + +/** + * A function that represents a screen, it is called frequently and should + * return a string consisting of commands to run. + * + * @interface IScreen + * @type function + * @param {Number} modulation A number between 0 and 255 representing the current + * step of the modulation + * @param {Number} width The width of the screen + * @param {Number} height The height of the screen + * @param {IRenderer} renderer The renderer used to colorize the scfeen + * @return {String} The commands used to render the screen elements + */ + +/** + * A function that represents a renderer, it should take in a color in RGB and + * return a string with the appropriate code to colorize the terminal. + * + * @interface IRenderer + * @type function + * @param {Number} red The red component of the color between 0 and 255 + * @param {Number} green The green component of the color between 0 and 255 + * @param {Number} blue The green component of the color between 0 and 255 + * @return {String} The commands used to colorize the terminal + */ + +/** + * The main application for tomato sauce. Listens for connections and serves + * random combinations of screens and renderers + * + * 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. + * + * @class TomatoSauce + * @extends EventEmitter + * + * @param {object} config the configuration object used to extend the properties. + * + * @property {Array} screens an array of screens available to serve + * @property {Array} renderers an array of renderers available to colorize + * @property {Number} [port=9999] the port to listen on + * @property {Number} [frequency=333] how often to update the screen + * @property {Number} [modulation=5] number between 0-255 depicting current modulation step + */ 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); - } -}; + constructor(config = {}) { + + super(); + + this.screens = []; + this.renderers = []; + + // Defaults. + this.port = 9999; + this.frequency = 333; + this.modulation = 5; + + Object.assign(this, config); + } + + /** + * Main entry point, initializes the server and binds events for connections + * + * @function run + * @instance + * @memberof TomatoSauce + */ + 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, () => { + + this.emit('listening', { + data: { + server + } + }); + }); + } + + // Binds the connection and error events + + _bindEvents() { + + // Send the error event all the way up. + this.server.on('error', (error) => { + + this.emit('error', { + data: { + error + } + }); + }); + + // Send the error event all the way up. + this.server.on('connection', (socket) => { -// Defaults. -TomatoSauce.prototype.port = 9999; -TomatoSauce.prototype.frequency = 333; -TomatoSauce.prototype.modulation = 5; + this._renderScreen(socket); + }); + } + + // Obtains viewport size, and initializes a random screen with a random + // renderer, setting the required interval to draw. + + _renderScreen(socket) { + + const connectionData = { + width: null, + height: null, + modulation: 0, + screen: this._getScreen(), + renderer: this._getRenderer(), + socket + }; + let interval = null; + + socket.write(internals.kNAWSRequest); + + socket.on('data', (data) => { + + if (data.slice(0, 6).compare(internals.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(internals.kEscape) === 0) { + socket.write('\n'); + clearInterval(interval); + socket.end(); + } + }); + } + + // Resets the cursor, gets a frame and sends it to the socket. + + _writeMessage(connectionData) { + + const payload = connectionData.screen(connectionData.modulation, connectionData.width, connectionData.height, connectionData.renderer); + const 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); + } +}; module.exports = TomatoSauce;