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 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<IScreen>} screens an array of screens available to serve
+ * @property {Array<IRenderer>} 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;