UNPKG

message-relay-services

Version:

A simple server to route message to other services, such as email, admin, etc.

347 lines (309 loc) 11.5 kB
'use strict'; const fs = require('fs') const Communicator = require('./common_communicator') const net = require('net'); const tls = require('tls'); const PORT = 1234; const HOST = 'localhost'; const DEFAULT_MAX_RECONNECT = 20 const DEFAULT_RECONNECT_WAIT = 5 // ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- // ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- // ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- /** * Exposes the communicator class to standard TCP or TLS connections. * Useful for connection to an ServerUDP instance */ class Client extends Communicator { constructor(conf,wrapper) { super(conf,wrapper) } // ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- /** * * @param {object} conf */ _init_members(conf) { this.socket = null // let uds_path = conf.UDS_path || conf.uds_path if ( uds_path === undefined ) { this.port = conf ? conf.port || PORT : PORT this.address = conf ? conf.address || HOST : HOST } else { this.UDS_path = uds_path } // this.use_tls = ((conf.tls !== undefined) && (conf.tls !== false)) || ((conf.default_tls !== undefined) && (conf.default_tls !== false)) this.tls_conf = conf.tls if ( this.tls_conf ) { if ( this.tls_conf.client_key === false || this.tls_conf.server_cert === false ) { if ( this.tls_conf.preloaded.client_key === false || this.tls_conf.preloaded.server_cert === false ) { this.use_tls = false } } } this.default_tls = conf.default_tls if ( !(this.use_tls) ) { this.use_tls = this.default_tls ? this.default_tls : false } this.extended_tls_options = ((conf.extended_tls_options !== undefined) && (conf.extended_tls_options !== false)) ? conf.extended_tls_options : false // this.send_on_reconnect = conf ? conf.send_on_reconnect || false : false // this.attempt_reconnect = false this.reconnect_wait = DEFAULT_RECONNECT_WAIT this.max_reconnect = DEFAULT_MAX_RECONNECT this.reconnect_count = 0 // if ( this.use_tls ) { this.preloaded_tls_keys = this._load_tls_keys() } if ( this.use_tls === undefined ) { this.use_tls = false } } /** * * @returns {object} - tls options use in the call to `connect` */ _load_tls_keys() { // let base = process.cwd() // // allow exceptions to be thrown if ( this.tls_conf ) { // let client_key = false if ( this.tls_conf.preloaded && this.tls_conf.preloaded.client_key ) { client_key = this.tls_conf.preloaded.client_key } else { client_key = fs.readFileSync(`${base}/${this.tls_conf.client_key}`) } // let client_cert = false if ( this.tls_conf.preloaded && this.tls_conf.preloaded.client_cert ) { client_cert = this.tls_conf.preloaded.client_cert } else { client_cert = fs.readFileSync(`${base}/${this.tls_conf.client_cert}`) } // let server_cert = false if ( this.tls_conf.preloaded && this.tls_conf.preloaded.server_cert ) { server_cert = this.tls_conf.preloaded.server_cert } else { if ( Array.isArray(this.tls_conf.server_cert) ) { server_cert = [] for ( let cert_file of this.tls_conf.server_cert ) { let one_cert = fs.readFileSync(`${base}/${cert_file}`) server_cert.push(one_cert) } } else { server_cert = fs.readFileSync(`${base}/${this.tls_conf.server_cert}`) } } let tls_options = { // Necessary only if the server requires client certificate authentication. key: client_key, cert: client_cert, // Necessary only if the server uses a self-signed certificate. ca: Array.isArray(server_cert) ? server_cert : [ server_cert ], // Necessary only if the server's cert isn't for "localhost". checkServerIdentity: () => { return null; }, }; return tls_options } // return false } // ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- /** * * @param {object} conf */ _create_connection(conf) { if ( this.files_only ) { (async () => { await this._setup_file_output(conf) })() } else { this.attempt_reconnect = (conf.attempt_reconnect !== undefined) ? conf.attempt_reconnect : false if ( this.attempt_reconnect ) { this._configure_reconnect(conf) } if ( conf._connection_manager && conf._connect_label ) { let c_manager = conf._connection_manager if ( typeof c_manager.add_waiting_connection === 'function' ) { // let connector_cb = (conf) => { // bind this to the lambda this._connect() this._setup_connection_handlers(this,conf) return this } // -- add_waiting_connection c_manager.add_waiting_connection(conf._connect_label,conf,connector_cb) // label,conf,callback } } else { this._connect() this._setup_connection_handlers(this,conf) } } } // /** * * @param {object} conf * @returns */ _init(conf) { // if ( conf === undefined ) { console.log("message relay client: cannot initialize -- no configuration") return; } this._init_members(conf) this._create_connection(conf) } // ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- // // // // // // /** * This is the connect method's response to a connection being made. */ _connection_handler(dont_emit) { this.writer = this.socket this.wrap_event(this.address || this.UDS_path) this.reconnect_count = 0 if ( this.UDS_path === undefined ) { console.log(`Client connected to: ${this.address} : ${this.port}`); } else { console.log(`Client connected to: ${this.UDS_path}`); } if ( this.files_going ) { // then shunting had to be set to true.. file_only has to be false super.restore_send(this.send_on_reconnect) } if ( dont_emit === undefined ) { if ( this.UDS_path === undefined ) { this.emit('client-ready',this.address,this.port) } else { this.emit('client-ready',this.UDS_path,'UDS') } } } // CONNECTION /** * Makes the actual connection using TPC or TLS. In node.js, this is the distinction between new Socket().connect() and tls.connect() */ _connect() { let connect_options = null if ( this.UDS_path !== undefined ) { connect_options = { 'path' : this.UDS_path } } else { connect_options = { 'port' : this.port, 'host' : this.address } } // PUBLIC if ( this.use_tls === false ) { this.socket = new net.Socket(); this.socket.connect(connect_options,() => { this._connection_handler() }); } else { // ENCRYPTED TLS if ( this.default_tls ) { this.socket = tls.connect(connect_options,() => { if ( this.socket.authorized ) { this._connection_handler() } else { this.socket.end() } this.writer = this.socket }); } else { const tls_options = this.preloaded_tls_keys; if ( this.extended_tls_options !== false ) { tls_options = Object.assign({},tls_options,this.extended_tls_options) } this.socket = tls.connect(connect_options, tls_options, () => { if ( this.socket.authorized ) { this._connection_handler() } else { this.socket.end() } this.writer = this.socket }); } } } // SET UP CONNECTION AND HANDLERS on('close'...) on('data'...) on('error'...) // _setup_connection_handlers // /** * * @param {object} client * @param {object} conf */ _setup_connection_handlers(client,conf) { // // HANDLERS client.socket.on('close', (onErr) => { if ( onErr ) { console.log(`got a closing error on ${client.address} : ${client.port}`) } this.unwrap_event(this.address) console.log('Client closed'); if ( client.attempt_reconnect ) { client._attempt_reconnect(conf) } }) // client.socket.on('data',(data) => { this.client_add_data_and_react(data) }); // let self = this client.socket.on('error',async (err) => { this.unwrap_event(this.address) console.log(__filename) console.log(err); if ( client.attempt_reconnect ) { if ( client.reconnect_count < client.max_reconnect ) { return; } } else if ( !(client.file_shunting) && (err.code === "ECONNREFUSED") ) { self.emit('client-connect-error',self.address,self.port,err.code) } if ( client.file_shunting ) { await client._start_file_shunting(conf) } }) // } // RECONNECTION ATTEMPTS /** * * @param {object} conf */ _configure_reconnect(conf) { this.max_reconnect = (conf.max_reconnect !== undefined) ? conf.max_reconnect : this.max_reconnect this.reconnect_wait = (conf.reconnect_wait !== undefined) ? conf.reconnect_wait : this.reconnect_wait if ( typeof this.reconnect_wait === "string" ) { this.reconnect_wait = parseInt(this.reconnect_wait) } this.reconnect_wait = this.reconnect_wait*1000 this.reconnect_count = 0 } /** * * @param {object} conf */ _attempt_reconnect(conf) { this.reconnect_count++ if ( this.reconnect_count < this.max_reconnect ) { setTimeout(() => { this._setup_connection_handlers(this,conf) },this.reconnect_wait) } } closeAll() { this.socket.destroy(); } } // module.exports = Client; module.exports.Communicator = Communicator