Source: models/Connection.js

/**
 * @author Serge Babayan
 * @module models/Connection
 * @requires util/Logger
 * @requires net
 * @requires util
 * @requires events
 * @requires config/advanced-config
 * @copyright Waterloo Aerial Robotics Group 2016
 * @licence https://raw.githubusercontent.com/UWARG/WARG-Ground-Station/master/LICENSE
 * @description Represents a TCP connection. This is used as a wrapper for the connection to the
 * data relay
 * @extends EventEmitter
 * @see https://nodejs.org/api/net.html
 */

var net = require('net');
var util = require('util');
var EventEmitter = require('events');
var Logger = require('../util/Logger');
var advanced_config = require('../../config/advanced-config');

/**
 * @param options Required options to initialize the TCP connection
 * @param options.name The unique name of the connection
 * @param options.host The hostname (ie. 192.168.1.101)
 * @param options.port The port for the connection (ie. 3000)
 * @throws {Error} If one of the parameters weren't provided
 * @constructor
 */
var Connection = function (options) {
  if (options && options.name && options.host && options.port) {
    this.name = options.name;
    this.host = options.host;
    this.port = options.port;
  }
  else {
    throw new Error("Connection name,host, and port parameters are all required!")
  }

  /**
   * Whether the connection has been closed
   * @type {boolean}
   */
  this.closed = true;

  /**
   * Timeout for the socket in milliseconds. Defaults to 5 seconds
   * @type {number}
   */
  this.timeout = 5000;

  // Initialize necessary properties from `EventEmitter` in this instance
  EventEmitter.call(this);

  this.setMaxListeners(advanced_config.get('connection_max_listeners'));

  /**
   * Attempts to connect to the TCP socket
   */
  this.connect = function () {
    Logger.info('Attempting to connect to ' + this.name + ' at ' + this.host + ':' + this.port);
    this.socket.connect(this.port, this.host);
  };

  /**
   * Disconnects from the TCP socket
   */
  this.disconnect = function () {
    Logger.info('Disconnecting from ' + this.name + ' at ' + this.host + ':' + this.port);
    this.socket.destroy();
  };

  /**
   * Reconnects to the socket (same as doing a disconnect and then a connect)
   */
  this.reconnect = function () {
    this.disconnect();
    this.connect();
  };

  /**
   * Sets the timeout for the socket connection in milliseconds
   * @function setTimeout
   * @param {int} timeout
   */
  this.setTimeout = function(timeout){
    this.socket.setTimeout(timeout);
  };

  /**
   * Attempts to send data through the socket
   * @param data {Object} The data to send
   * @fires Connection:write
   */
  this.write = function (data) {
    this.socket.write(data, 'utf8', function (error) {
      if (!error) {
        /**
         * Emitted if data was successfully sent through the socket. Passes back the data sent
         * @event Connection:write
         * @type {Object}
         */
        this.emit('write', data);
        Logger.info("Network " + this.name + " Sent: " + data);
      }
    }.bind(this));
  };

  /**
   * A reference to the socket.
   * @type {net.Socket}
   */
  this.socket = new net.Socket();
  this.socket.setTimeout(this.timeout);

  this.socket.on('connect', function () {
    /**
     * Emitted when the socket has successfully connected
     * @event Connection:connect
     * @type {null}
     */
    this.emit('connect');
    this.closed = false;
    Logger.info('Sucessfully connected to ' + this.name + ' with host ' + this.host + ' and port ' + this.port);
  }.bind(this));

  this.socket.on('error', function (error) {
    this.closed = true;
    Logger.error('Problem with ' + this.name + ' connection (host: ' + this.host + ',port:' + this.port + ')\n'
      + error.toString());

    /**
     * Emitted when an error has occurred on the socket. Does not throw an error if there are no listeners
     * @event Connection:socket_error
     * @type {Object}
     */
    this.emit('socket_error', error); //NOTE: named socket_error and not error so as to not throw an exception
  }.bind(this));

  /**
   * Emitted when the socket has timed out (receiving no data but still connected)
   * @event Connection:timeout
   * @type {null}
   */
  this.socket.on('timeout', function () {
    this.emit('timeout');
    Logger.error('Timed out for ' + this.timeout/1000 + 's for ' + this.name + ' connection (host: ' + this.host + ',port:' + this.port + ')');
  }.bind(this));

  /**
   * Emitted when the socket has been closed. Passes true if it closed due to an error
   * @event Connection:close
   * @type {bool}
   */
  this.socket.on('close', function (had_error) {
    this.emit('close', had_error);
    this.closed = true;
    if (had_error) {
      Logger.error('Connection to  ' + this.name + ' closed due to an error: Not reconnecting');
    } else {
      Logger.warn('Connection to ' + this.name + ' closed: Not reconnecting');
    }
  }.bind(this));

  /**
   * Emitted when the socket has received a packet of data
   * @event Connection:data
   * @type {Buffer}
   */
  this.socket.on('data', function (data) {
    this.emit('data', data);
  }.bind(this));
};

util.inherits(Connection, EventEmitter);

module.exports = Connection;