import { Server } from 'net' import { unlink as fileDelete } from 'fs' import btc from 'better-try-catch' import ON_DEATH from 'death' //this is intentionally ugly import bunyan from 'bunyan' // import Stream from 'delimiter-stream' import JsonStream from './json-stream' export default class Socket extends Server { constructor (path={},opts={}) { super() if (typeof(path)!=='string') { if (!path.host || !path.port) opts = path this.listen_opts = { host: path.host || '0.0.0.0', port: path.port || 8080} } else this.listen_opts = { path: path } this.packet = { // default packet processing - simple echo server _process: (packet) => { packet.res='echoed' return packet } } // Change to environment based configuration for logger this.log_file=opts.log_file || './socket.log' this.log_opts = {streams:[]} this.log_opts.name = opts.name ? opts.name : 'uci-socket' // if (opts.log===1)// this.log_opts.streams.push({level: 'info',path: this.log_file }) if (opts.log) this.log_opts.streams.push({level: 'info',stream: process.stdout}) this.log = bunyan.createLogger(this.log_opts) //self binding this.listen = this.listen.bind(this) this.create = this.create.bind(this) } // end constructor async create () { return new Promise( async (resolve,reject) => { ON_DEATH( async () => { this.log.info('\nhe\'s dead jim') await this.destroy() }) process.once('SIGUSR2', async () => { await this.destroy process.kill(process.pid, 'SIGUSR2') }) this.on('error', async (err) => { // recover from socket file that was not removed if (err.code === 'EADDRINUSE') { let path = this.listen_opts.path if (path) { // if TCP socket should already be dead this.log.info({socket: path}, 'already exists...deleting') await fileDelete(path) return await this.listen(this.listen_opts) } } // otherwise fatally exit this.log.info(err, 'creating socket') reject(err) }) let [err, res] = await btc(this.listen)(this.listen_opts) if (err) reject(err) resolve(res) }) // end promise } // end create async listen (opts) { super.listen(opts, async (err, res) => { if (err) return err // this gets called for each client connection and is unique to each this.on('connection', (socket) => { const stream = new JsonStream() this.log.info('new consumer connecting sending handshake') socket.write(stream.serialize({ready:true})) socket.on('data', stream.onData) stream.on('message', messageProcess.bind(this)) async function messageProcess (packet) { socket.write(stream.serialize(await this.packet._process(packet))) } }) // end connected consumer this.log.info({socket: this.listen_opts},'socket created') return res }) // end listen callback } async destroy () { this.log.info('closing down socket') await this.close() this.log.info('all connections closed....exiting') process.exit() } registerPacketProcessor (func) { this.packet._process = func } } // end class