X-Git-Url: https://git.r.bdr.sh/rbdr/tomato-sauce/blobdiff_plain/a41763dd463339765081c1f968a7b93b8ccf572f..refs/heads/main:/lib/tomato_sauce.js?ds=inline diff --git a/lib/tomato_sauce.js b/lib/tomato_sauce.js index 0ff087a..4a65ea1 100644 --- a/lib/tomato_sauce.js +++ b/lib/tomato_sauce.js @@ -5,89 +5,150 @@ 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: new Buffer([0xFF, 0xF4, 0XFF, 0xFD, 0x06]), // IAC IP IAC DO TIMING_MARK + kNAWSRequest: new Buffer([0xFF, 0xFD, 0X1F]), // IAC DO NAWS + kNAWSResponse: new Buffer([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) { + constructor(config = {}) { + super(); this.screens = []; this.renderers = []; - Object.assign(this, config || {}); + // Defaults. + this.port = 9999; + this.frequency = 333; + this.modulation = 5; + + Object.assign(this, config); } - // Here's where things get started. - run () { + /** + * 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 () { + + _startServer() { + const server = Net.createServer(); this.server = server; - server.listen(this.port, function () { + server.listen(this.port, () => { + this.emit('listening', { data: { - server: server + server } }); - }.bind(this)); + }); } - _bindEvents () { + // Binds the connection and error events + + _bindEvents() { + // Send the error event all the way up. - this.server.on('error', function (err) { + this.server.on('error', (error) => { + this.emit('error', { data: { - error: err + error } }); - }.bind(this)); + }); // Send the error event all the way up. - this.server.on('connection', function (socket) { + this.server.on('connection', (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 = { + + _renderScreen(socket) { + + const connectionData = { width: null, height: null, modulation: 0, screen: this._getScreen(), renderer: this._getRenderer(), - socket: socket + socket }; let interval = null; - socket.write(kNAWSRequest); + socket.write(internals.kNAWSRequest); + + socket.on('data', (data) => { - socket.on('data', function (data) { - if (data.slice(0, 6).compare(kNAWSResponse) === 0) { + 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)); @@ -95,35 +156,34 @@ const TomatoSauce = class TomatoSauce extends EventEmitter { interval = setInterval(this._writeMessage.bind(this, connectionData), this.frequency); } - if (data.compare(kEscape) === 0) { + if (data.compare(internals.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 + + _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 () { + _getScreen() { + return Util.pickRandom(this.screens); } - _getRenderer () { + _getRenderer() { + return Util.pickRandom(this.renderers); } }; -// Defaults. -TomatoSauce.prototype.port = 9999; -TomatoSauce.prototype.frequency = 333; -TomatoSauce.prototype.modulation = 5; - module.exports = TomatoSauce;