uci-socket/src/socket.mjs

122 lines
3.5 KiB
JavaScript

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'
export default class Socket extends Server {
constructor (path, opts={}) {
super()
// set or tcp socket
if (typeof(path)!=='string') {
opts = path
this.listen_opts = { host: opts.host || '127.0.0.1', port: opts.port || 8080}
} else this.listen_opts = { path: path }
this._pp = opts.packetProcessor || 'processPacket'
// logging
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)
//binding
} // end constructor
async create (app={}) {
// first set the packet process
this.pp = this.pp || this._pp
if (Object.keys(app).length === 0) app = this
else app.pp = app.pp || this._pp
// set a default processor if none provided
if (!app[app.pp]) {
app.pp = 'processPacket' // reset in case alt function is missing
app.processPacket = async (packet) => {
packet.res='echoed'
return packet }
}
// console.log('socket processing app ',app[app.pp])
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.bind(this)(this.listen_opts,app)
}
}
// otherwise fatally exit
this.log.info(err, 'creating socket')
reject(err)
})
let [err, res] = await btc(this.listen).bind(this)(this.listen_opts,app)
if (err) reject(err)
resolve(res)
}) // end promise
} // end create
async listen (opts,app) {
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', async function (packet) {
// console.log('incoming packet from consumer',packet)
// console.log('socket processing app ',app[app.pp])
socket.write(stream.serialize(await app[app.pp].bind(app)(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()
} // end destroy
} // end class