From bb475ab00fa970d4e06096e3cce99a1485b79cf1 Mon Sep 17 00:00:00 2001 From: Tony Brix Date: Thu, 18 Feb 2021 22:45:14 -0600 Subject: [PATCH 1/2] fix: emit error event if other error events are not listened to --- lib/client/client.js | 16 ++++++- lib/errors/index.js | 3 ++ test/client.test.js | 103 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 119 insertions(+), 3 deletions(-) diff --git a/lib/client/client.js b/lib/client/client.js index d090cca..bdff844 100644 --- a/lib/client/client.js +++ b/lib/client/client.js @@ -1029,9 +1029,9 @@ Client.prototype.connect = function connect () { self.log.debug('failed to connect after %d attempts', failAfter) // Communicate the last-encountered error if (err instanceof ConnectionError) { - self.emit('connectTimeout', err) + self.emitError('connectTimeout', err) } else if (err.code === 'ECONNREFUSED') { - self.emit('connectRefused', err) + self.emitError('connectRefused', err) } else { self.emit('error', err) } @@ -1277,3 +1277,15 @@ Client.prototype._sendSocket = function _sendSocket (message, return callback(e) } } + +Client.prototype.emitError = function emitError (event, err) { + if (event !== 'error' && err && this.listenerCount(event) === 0) { + if (typeof err === 'string') { + err = event + ': ' + err + } else if (err.message) { + err.message = event + ': ' + err.message + } + this.emit('error', err) + } + this.emit(event, err) +} diff --git a/lib/errors/index.js b/lib/errors/index.js index a6c3826..07c5745 100644 --- a/lib/errors/index.js +++ b/lib/errors/index.js @@ -32,6 +32,9 @@ Object.defineProperties(LDAPError.prototype, { get: function getMessage () { return this.lde_message || this.name }, + set: function setMessage (message) { + this.lde_message = message + }, configurable: false }, dn: { diff --git a/test/client.test.js b/test/client.test.js index 40a93ce..d248abf 100644 --- a/test/client.test.js +++ b/test/client.test.js @@ -367,7 +367,7 @@ tap.test('createClient', t => { ) }) - tap.test('exception from bad createClient parameter (issue #418)', t => { + t.test('exception from bad createClient parameter (issue #418)', t => { try { // This port number is totally invalid. It will cause the URL parser // to throw an exception that should be caught. @@ -387,6 +387,9 @@ tap.test('createClient', t => { ], connectTimeout: 1 }) + client.on('connectTimeout', () => {}) + client.on('connectError', () => {}) + client.on('connectRefused', () => {}) t.equal(client.urls.length, 2) }) @@ -1513,6 +1516,8 @@ tap.test('connection refused', function (t) { url: `ldap://0.0.0.0:${unusedPortNumber}` }) + client.on('connectRefused', () => {}) + client.bind('cn=root', 'secret', function (err, res) { t.true(err) t.type(err, Error) @@ -1531,6 +1536,8 @@ tap.test('connection timeout', function (t) { timeout: 1 }) + client.on('connectTimeout', () => {}) + let done = false setTimeout(function () { @@ -1549,3 +1556,97 @@ tap.test('connection timeout', function (t) { }) }) }) + +tap.only('emitError', function (t) { + t.test('connectTimeout', function (t) { + getPort().then(function (unusedPortNumber) { + const client = ldap.createClient({ + url: `ldap://example.org:${unusedPortNumber}`, + connectTimeout: 1, + timeout: 1 + }) + + const timeout = setTimeout(function () { + throw new Error('LDAPJS waited for the server for too long') + }, 2000) + + client.on('error', (err) => { + t.fail(err) + }) + client.on('connectTimeout', (err) => { + t.true(err) + t.type(err, Error) + t.equals(err.message, 'connection timeout') + clearTimeout(timeout) + t.end() + }) + + client.bind('cn=root', 'secret', () => {}) + }) + }) + + t.test('connectTimeout to error', function (t) { + getPort().then(function (unusedPortNumber) { + const client = ldap.createClient({ + url: `ldap://example.org:${unusedPortNumber}`, + connectTimeout: 1, + timeout: 1 + }) + + const timeout = setTimeout(function () { + throw new Error('LDAPJS waited for the server for too long') + }, 2000) + + client.on('error', (err) => { + t.true(err) + t.type(err, Error) + t.equals(err.message, 'connectTimeout: connection timeout') + clearTimeout(timeout) + t.end() + }) + + client.bind('cn=root', 'secret', () => {}) + }) + }) + + t.test('connectRefused', function (t) { + getPort().then(function (unusedPortNumber) { + const client = ldap.createClient({ + url: `ldap://0.0.0.0:${unusedPortNumber}` + }) + + client.on('error', (err) => { + t.fail(err) + }) + client.on('connectRefused', (err) => { + t.true(err) + t.type(err, Error) + t.equals(err.message, `connect ECONNREFUSED 0.0.0.0:${unusedPortNumber}`) + t.equals(err.code, 'ECONNREFUSED') + t.end() + }) + + client.bind('cn=root', 'secret', () => {}) + }) + }) + + t.test('connectRefused to error', function (t) { + getPort().then(function (unusedPortNumber) { + const client = ldap.createClient({ + url: `ldap://0.0.0.0:${unusedPortNumber}` + }) + + client.on('error', (err) => { + t.true(err) + t.type(err, Error) + t.equals(err.message, `connectRefused: connect ECONNREFUSED 0.0.0.0:${unusedPortNumber}`) + t.equals(err.code, 'ECONNREFUSED') + t.end() + }) + + client.bind('cn=root', 'secret', () => {}) + }) + }) + + t.end() +}) From 0dab63cef932255852fa9224f9d916d33cb85074 Mon Sep 17 00:00:00 2001 From: Tony Brix Date: Tue, 23 Feb 2021 22:30:47 -0600 Subject: [PATCH 2/2] docs: add client events table --- docs/client.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/client.md b/docs/client.md index d72f560..62a55cb 100644 --- a/docs/client.md +++ b/docs/client.md @@ -20,6 +20,10 @@ The code to create a new client looks like: url: ['ldap://127.0.0.1:1389', 'ldap://127.0.0.2:1389'] }); + client.on('error', (err) => { + // handle connection error + }) + You can use `ldap://` or `ldaps://`; the latter would connect over SSL (note that this will not use the LDAP TLS extended operation, but literally an SSL connection to port 636, as in LDAP v2). The full set of options to create a @@ -70,6 +74,25 @@ more sophisticated control, you can provide an Object with the properties `failAfter` (default: `Infinity`). After the reconnect you maybe need to [bind](#bind) again. +## Client events + +The client is an `EventEmitter` and can emit the following events: + +|Event |Description | +|---------------|----------------------------------------------------------| +|error |General error | +|connectRefused |Server refused connection. Most likely bad authentication | +|connectTimeout |Server timeout | +|connectError |Socket connection error | +|setupError |Setup error after successful connection | +|socketTimeout |Socket timeout | +|resultError |Search result error | +|timeout |Search result timeout | +|destroy |After client is disconnected | +|end |Socket end event | +|close |Socket closed | +|connect |Client connected | +|idle |Idle timeout reached | ## Common patterns