Source: client-network.js

/**
 * Network Layer module. It implements an abstraction layer over the network.
 * Implements the ClientNetwork class.
 * @module ClientNetwork
 */
const dgram = require('dgram');
const EventEmitter = require('events');
const net = require('net');
const util = require('util');


const Response = require('./response');
const Request = require('./request');
const utilQ4S = require('./util');

/**
 * Network layer class. It emmits events for each recieved message. Implements
 * functions to send messages over the network
 * @extends EventEmitter
 * @fires ClientNetwork#handshake-Res
 * @fires ClientNetwork#TCP-Res
 * @fires ClientNetwork#TCP-Req
 * @fires ClientNetwork#UDP-Res
 * @fires ClientNetwork#UDP-Req
 */
class ClientNetwork extends EventEmitter {
  /** Constructor for the ClientNetwork. Only inicialices the super class. */
  constructor() {
    super();
    /**
     * The handshake socket.
     * @member {net.Socket}
     */
    this.handshakeSocket = new net.Socket();
    this.handshakeSocket.connectPromised = util.promisify(
        this.handshakeSocket.connect
    );
    this.handshakeSocket.on('data', (data) => {
      try {
        this.emit('handshake-Res', Response.fromString(data.toString('utf-8')));
      } catch (e) {
        this.emit('error', e);
      }
    });
    this.handshakeSocket.on('error', (err) => {
      this.closeHandshake();
      this.emit('error', err);
    });
    /**
     * The TCP Q4S socket.
     * @member {net.Socket}
     */
    this.TCPSocket = new net.Socket();
    this.TCPSocket.connectPromised = util.promisify(this.TCPSocket.connect);
    this.TCPSocket.on('data', (data) => {
      const msg = data.toString('utf-8');

      if (utilQ4S.isRequest(msg)) {
        try {
          this.emit('TCP-Req', Request.fromString(msg));
        } catch (e) {
          this.TCPSocket.write(Response.genRes(400).toString(), 'utf8');
        }
      } else {
        try {
          this.emit('TCP-Res', Response.fromString(msg));
        } catch (e) {
          this.emit('error', e);
        }
      }
    });
    this.TCPSocket.on('error', (err) => {
      this.closeNetwork();
      this.emit('error', err);
    });
    /**
     * The UDP Q4S socket.
     * @member {dgram.Socket}
     */
    this.UDPSocket = dgram.createSocket('udp4');

    this.UDPSocket.bindPromised = util.promisify(this.UDPSocket.bind);
    this.UDPSocket.on('message', (msg, rinfo) => {
      const now = new Date();
      const msgString = msg.toString('utf-8');

      if (utilQ4S.isRequest(msgString)) {
        try {
          this.emit('UDP-Req', Request.fromString(msgString), now);
        } catch (e) {
          sendUDP(Response.genRes(400));
        }
      } else {
        try {
          this.emit('UDP-Res', Response.fromString(msgString), now);
        } catch (e) {
          this.emit('error', e);
        }
      }
    });
    this.UDPSocket.on('error', (err) => {
      this.closeNetwork();
      this.emit('error', err);
    });
  }
  /**
   * Async method to create the handshake socket. After completition the
   * sendHandshakeTCP method can be called to send messages. After completition
   * the class starts to emit events.
   * @param {string} ip - The string containing the ip to connect
   * @param {string} port - Port to connect
   * @param {string} localPort - Origin port for the handshake connection.
   * @return {Promise} On success nothing, on error the network error.
   */
  async initHandshakeSocket(ip, port, localPort) {
    const handshakeSocketOps = {
      host: ip,
      port: port,
      localPort: localPort,
    };
    await this.handshakeSocket.connectPromised(handshakeSocketOps);
    return;
  }
  /**
   * Async method to create both q4s sockets for the connection.
   * @param {string} ip - The string containing the ip to connect
   * @param {Number} port - TCP port to connect
   * @param {Number} localPort - Origin port for the TCP connection
   * @param {Number} portUDP - Port for UDP communication
   * @param {Number} listeningPortUDP - Port to listen for UDP messages.
   * @return {Promise} On success nothing, on error the network error.
   */
  async initQ4sSocket(ip, port, localPort, portUDP, listeningPortUDP) {
    const TCPSocketOps = {
      host: ip,
      port: port,
      family: 4,
      localPort: localPort,
    };
    this.UDPSocketOps = {
      host: ip,
      port: portUDP,
      listeningPort: listeningPortUDP,
    };
    await Promise.all([
      this.TCPSocket.connectPromised(TCPSocketOps),
      this.UDPSocket.bindPromised(listeningPortUDP),
    ]);
    return;
  }
  /**
   * Send a message trough the handshake socket.
   * @param {(ReSQ4S|Request)} msg - The request or response to send.
   */
  sendHandshakeTCP(msg) {
    this.handshakeSocket.write(msg.toString(), 'utf8');
    return;
  }
  /**
   * Send a message trough the q4s TCP socket.
   * @param {(ReSQ4S|Request)} msg - The request or response to send.
   */
  sendTCP(msg) {
    this.TCPSocket.write(msg.toString(), 'utf8');
    return;
  }
  /**
   * Send a message trough the q4s UDP socket.
   * @param {(Response|Request)} msg - The request or response to send.
   */
  sendUDP(msg) {
    this.UDPSocket.send(
        msg.toString(),
        this.UDPSocketOps.port,
        this.UDPSocketOps.host
    );
    return;
  }
  /**
   * Close all communications. Call to clean up at the end.
   */
  closeNetwork() {
    this.handshakeSocket.end();
    this.TCPSocket.end();
    this.UDPSocket.close();
  }
  /**
   * Close the handshake socket.
   */
  closeHandshake() {
    this.handshakeSocket.end();
  }
}
module.exports = ClientNetwork;