'use strict'; var serversMap = module.exports._serversMap = {}; var dgramMap = module.exports._dgramMap = {}; var PromiseA = require('bluebird'); module.exports.addTcpListener = function (port, handler) { return new PromiseA(function (resolve, reject) { var stat = serversMap[port]; if (stat) { if (stat._closing) { stat.server.destroy(); } else { // We're already listening on the port, so we only have 2 options. We can either // replace the handler or reject with an error. (Though neither is really needed // if the handlers are the same). Until there is reason to do otherwise we are // opting for the replacement. stat.handler = handler; resolve(); return; } } var enableDestroy = require('server-destroy'); var net = require('net'); var resolved; var server = net.createServer({allowHalfOpen: true}); stat = serversMap[port] = { server: server , handler: handler , _closing: false }; // Add .destroy so we can close all open connections. Better if added before listen // to eliminate any possibility of it missing an early connection in it's records. enableDestroy(server); server.on('connection', function (conn) { conn.__port = port; conn.__proto = 'tcp'; stat.handler(conn); }); server.on('close', function () { console.log('TCP server on port %d closed', port); delete serversMap[port]; }); server.on('error', function (e) { if (!resolved) { reject(e); } else if (handler.onError) { handler.onError(e); } else { throw e; } }); server.listen(port, function () { resolved = true; resolve(); }); }); }; module.exports.closeTcpListener = function (port, timeout) { return new PromiseA(function (resolve) { var stat = serversMap[port]; if (!stat) { resolve(); return; } stat._closing = true; var timeoutId; if (timeout) { timeoutId = setTimeout(() => stat.server.destroy(), timeout); } stat.server.once('close', function () { clearTimeout(timeoutId); resolve(); }); stat.server.close(); }); }; module.exports.destroyTcpListener = function (port) { var stat = serversMap[port]; if (stat) { stat.server.destroy(); } }; module.exports.listTcpListeners = function () { return Object.keys(serversMap).map(Number).filter(function (port) { return port && !serversMap[port]._closing; }); }; module.exports.addUdpListener = function (port, handler) { return new PromiseA(function (resolve, reject) { var stat = dgramMap[port]; if (stat) { // we'll replace the current listener stat.handler = handler; resolve(); return; } var dgram = require('dgram'); var server = dgram.createSocket({type: 'udp4', reuseAddr: true}); var resolved = false; stat = dgramMap[port] = { server: server , handler: handler }; server.on('message', function (msg, rinfo) { msg._size = rinfo.size; msg._remoteFamily = rinfo.family; msg._remoteAddress = rinfo.address; msg._remotePort = rinfo.port; msg._port = port; stat.handler(msg); }); server.on('error', function (err) { if (!resolved) { delete dgramMap[port]; reject(err); } else if (stat.handler.onError) { stat.handler.onError(err); } else { throw err; } }); server.on('close', function () { delete dgramMap[port]; }); server.bind(port, function () { resolved = true; resolve(); }); }); }; module.exports.closeUdpListener = function (port) { var stat = dgramMap[port]; if (!stat) { return PromiseA.resolve(); } return new PromiseA(function (resolve) { stat.server.once('close', resolve); stat.server.close(); }); }; module.exports.listUdpListeners = function () { return Object.keys(dgramMap).map(Number).filter(Boolean); }; module.exports.listeners = { tcp: { add: module.exports.addTcpListener , close: module.exports.closeTcpListener , destroy: module.exports.destroyTcpListener , list: module.exports.listTcpListeners } , udp: { add: module.exports.addUdpListener , close: module.exports.closeUdpListener , list: module.exports.listUdpListeners } };