diff --git a/lib/portfinder.d.ts b/lib/portfinder.d.ts index e048be5..93650cf 100644 --- a/lib/portfinder.d.ts +++ b/lib/portfinder.d.ts @@ -28,6 +28,20 @@ interface PortFinderOptions { stopPort?: number; } +type SocketfinderCallback = (err: Error, socket: string) => void; + +interface SocketFinderOptions { + /** + * Mode to use when creating folder for socket if it doesn't exist + */ + mod?: number; + /** + * Path to the socket file to create + * (defaults to `${exports.basePath}.sock` if not provided) + */ + path?: string; +} + /** * The lowest port to begin any port search from. */ @@ -81,3 +95,9 @@ export function getPorts(count: number, options: PortFinderOptions, callback: (e * Responds a promise that resolves to an array of unbound ports on the current machine. */ export function getPortsPromise(count: number, options?: PortFinderOptions): Promise>; + +export function getSocket(options: SocketFinderOptions): Promise; +export function getSocket(callback: SocketfinderCallback): void; +export function getSocket(options: SocketFinderOptions, callback: SocketfinderCallback): void; + +export function getSocketPromise(options?: SocketFinderOptions): Promise; diff --git a/lib/portfinder.js b/lib/portfinder.js index e6fbbba..473a634 100644 --- a/lib/portfinder.js +++ b/lib/portfinder.js @@ -232,7 +232,7 @@ exports.getPort = function (options, callback) { }); }); } else { - return internals.getPort(options, callback); + internals.getPort(options, callback); } }; @@ -286,7 +286,7 @@ exports.getPorts = function (count, options, callback) { }); }); } else { - return internals.getPorts(count, options, callback); + internals.getPorts(count, options, callback); } }; @@ -298,19 +298,7 @@ exports.getPorts = function (count, options, callback) { // exports.getPortsPromise = exports.getPorts; -// -// ### function getSocket (options, callback) -// #### @options {Object} Settings to use when finding the necessary port -// #### @callback {function} Continuation to respond to when complete. -// Responds with a unbound socket using the specified directory and base -// name on the current machine. -// -exports.getSocket = function (options, callback) { - if (!callback) { - callback = options; - options = {}; - } - +internals.getSocket = function (options, callback) { options.mod = options.mod || parseInt(755, 8); options.path = options.path || exports.basePath + '.sock'; @@ -337,7 +325,7 @@ exports.getSocket = function (options, callback) { // next socket. // options.path = exports.nextSocket(options.path); - exports.getSocket(options, callback); + internals.getSocket(options, callback); } }); } @@ -386,6 +374,37 @@ exports.getSocket = function (options, callback) { : checkAndTestSocket(); }; +// +// ### function getSocket (options, callback) +// #### @options {Object} Settings to use when finding the necessary port +// #### @callback {function} Continuation to respond to when complete. +// Responds with a unbound socket using the specified directory and base +// name on the current machine. +// +exports.getSocket = function (options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + + options = options || {}; + + if (!callback) { + return new Promise(function (resolve, reject) { + internals.getSocket(options, function (err, socketPath) { + if (err) { + return reject(err); + } + resolve(socketPath); + }); + }); + } else { + internals.getSocket(options, callback); + } +} + +exports.getSocketPromise = exports.getSocket; + // // ### function nextPort (port) // #### @port {Number} Port to increment from. diff --git a/test/port-finder-socket.test.js b/test/port-finder-socket.test.js index e86c2a0..8635d7e 100644 --- a/test/port-finder-socket.test.js +++ b/test/port-finder-socket.test.js @@ -25,7 +25,8 @@ function createServers (callback) { function (next) { const server = net.createServer(function () { }), name = base === 0 ? 'test.sock' : 'test' + base + '.sock'; - let sock = path.join(socketDir, name); + const socket = path.join(socketDir, name); + let sock = socket; // shamelessly stolen from foreverjs, // https://github.com/foreverjs/forever/blob/6d143609dd3712a1cf1bc515d24ac6b9d32b2588/lib/forever/worker.js#L141-L154 @@ -46,22 +47,19 @@ function createServers (callback) { server.listen(sock, next); base++; - servers.push(server); + servers.push([server, socket]); }, callback); } -function stopServers(callback, index) { - if (index < servers.length) { - servers[index].close(function (err) { - if (err) { - callback(err, false); - } else { - stopServers(callback, index + 1); +function stopServers(callback) { + _async.each(servers, function ([server, socket], next) { + server.close(function () { + if (process.platform === 'win32' && fs.existsSync(socket)) { + fs.unlinkSync(socket); } + next(); }); - } else { - callback(null, true); - } + }, callback); } function cleanup(callback) { @@ -74,7 +72,7 @@ function cleanup(callback) { if (fs.existsSync(badDir)) { fs.rmdirSync(badDir); } - stopServers(callback, 0); + stopServers(callback); } describe('portfinder', function () { @@ -88,60 +86,126 @@ describe('portfinder', function () { describe('with 5 existing servers', function () { beforeAll(function (done) { - createServers(function () { - portfinder.getSocket({ - path: path.join(badDir, 'test.sock'), - }, function () { - done(); - }); - }); + createServers(done); }); afterAll(function (done) { stopServers(done); }); - test('the getSocket() method should respond with the first free socket (test5.sock)', function (done) { - portfinder.getSocket({ - path: path.join(socketDir, 'test.sock'), - }, function (err, socket) { - expect(err).toBeNull(); - expect(socket).toEqual(path.join(socketDir, 'test5.sock')); - done(); + describe.each([ + ['getSocket()', false, portfinder.getSocket], + ['getSocket()', true, portfinder.getSocket], + ['getSocketPromise()', true, portfinder.getSocketPromise], + ])(`the %s method (promise: %p)`, function (name, isPromise, method) { + test('should respond with the first free socket (test5.sock)', function (done) { + if (isPromise) { + method({ + path: path.join(socketDir, 'test.sock') + }) + .then(function (socket) { + expect(socket).toEqual(path.join(socketDir, 'test5.sock')); + done(); + }) + .catch(function (err) { + done(err); + }); + } else { + method({ + path: path.join(socketDir, 'test.sock'), + }, function (err, socket) { + if (err) { + done(err); + return; + } + expect(err).toBeNull(); + expect(socket).toEqual(path.join(socketDir, 'test5.sock')); + done(); + }); + } }); }); }); describe('with no existing servers', function () { - describe('the getSocket() method', function () { + describe.each([ + ['getSocket()', false, portfinder.getSocket], + ['getSocket()', true, portfinder.getSocket], + ['getSocketPromise()', true, portfinder.getSocketPromise], + ])(`the %s method (promise: %p)`, function (name, isPromise, method) { test("with a directory that doesn't exist should respond with the first free socket (test.sock)", function (done) { - portfinder.getSocket({ - path: path.join(badDir, 'test.sock'), - }, function (err, socket) { - expect(err).toBeNull(); - expect(socket).toEqual(path.join(badDir, 'test.sock')); - done(); - }); + if (isPromise) { + method({ + path: path.join(badDir, 'test.sock'), + }) + .then(function (socket) { + expect(socket).toEqual(path.join(badDir, 'test.sock')); + done(); + }) + .catch(function (err) { + done(err); + }); + } else { + method({ + path: path.join(badDir, 'test.sock'), + }, function (err, socket) { + if (err) { + done(err); + return; + } + expect(err).toBeNull(); + expect(socket).toEqual(path.join(badDir, 'test.sock')); + done(); + }); + } }); test("with a nested directory that doesn't exist should respond with the first free socket (test.sock)", function (done) { - portfinder.getSocket({ - path: path.join(badDir, 'deeply', 'nested', 'test.sock'), - }, function (err, socket) { - expect(err).toBeNull(); - expect(socket).toEqual(path.join(badDir, 'deeply', 'nested', 'test.sock')); - done(); - }); + if (isPromise) { + method({ + path: path.join(badDir, 'deeply', 'nested', 'test.sock'), + }) + .then(function (socket) { + expect(socket).toEqual(path.join(badDir, 'deeply', 'nested', 'test.sock')); + done(); + }) + .catch(function (err) { + done(err); + }); + } else { + method({ + path: path.join(badDir, 'deeply', 'nested', 'test.sock'), + }, function (err, socket) { + expect(err).toBeNull(); + expect(socket).toEqual(path.join(badDir, 'deeply', 'nested', 'test.sock')); + done(); + }); + } }); - test('with a directory that exists should respond with the first free socket (test.sock)', function (done) { - portfinder.getSocket({ - path: path.join(socketDir, 'exists.sock'), - }, function (err, socket) { - expect(err).toBeNull(); - expect(socket).toEqual(path.join(socketDir, 'exists.sock')); - done(); - }); + // We don't use `test.sock` here due to some race condition on Windows in freeing the `test.sock` file + // when we close the servers. + test('with a directory that exists should respond with the first free socket (foo.sock)', function (done) { + if (isPromise) { + method({ + path: path.join(socketDir, 'foo.sock'), + }) + .then(function (socket) { + expect(socket).toEqual(path.join(socketDir, 'foo.sock')); + done(); + }) + .catch(function (err) { + done(err); + }); + } else { + method({ + path: path.join(socketDir, 'foo.sock'), + }, function (err, socket) { + expect(err).toBeNull(); + expect(socket).toEqual(path.join(socketDir, 'foo.sock')); + done(); + }); + } }); }); });