diff --git a/src/connection.ts b/src/connection.ts index 4241efc85..4c7f6225d 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -1052,7 +1052,7 @@ class Connection extends EventEmitter { throw new TypeError('The "config" argument is required and must be of type Object.'); } - if (typeof config.server !== 'string') { + if (typeof config.server !== 'string' && !config.options!.connector) { throw new TypeError('The "config.server" property is required and must be of type string.'); } @@ -1343,8 +1343,15 @@ class Connection extends EventEmitter { if (typeof config.options.connector !== 'function') { throw new TypeError('The "config.options.connector" property must be a function.'); } + if (config.server) { + throw new Error('Server and connector are mutually exclusive, but ' + config.server + ' and a connector function were provided'); + } + if (config.options.port) { + throw new Error('Port and connector are mutually exclusive, but ' + config.options.port + ' and a connector function were provided'); + } this.config.options.connector = config.options.connector; + this.config.options.port = undefined; } if (config.options.cryptoCredentialsDetails !== undefined) { @@ -1900,7 +1907,10 @@ class Connection extends EventEmitter { initialiseConnection() { const signal = this.createConnectTimer(); - if (this.config.options.port) { + if (this.config.options.connector) { + // port and multiSubnetFailover are not used when using a custom connector + return this.connectOnPort(0, false, signal, this.config.options.connector); + } else if (this.config.options.port) { return this.connectOnPort(this.config.options.port, this.config.options.multiSubnetFailover, signal, this.config.options.connector); } else { return instanceLookup({ @@ -1995,7 +2005,12 @@ class Connection extends EventEmitter { this.socket = socket; this.closed = false; - this.debug.log('connected to ' + this.config.server + ':' + this.config.options.port); + + const message = + 'connected to ' + this.config.server + ':' + this.config.options.port; + const customConnectorMessage = + 'connected via custom connector'; + this.debug.log(customConnector ? customConnectorMessage : message); this.sendPreLogin(); this.transitionTo(this.STATE.SENT_PRELOGIN); @@ -2073,8 +2088,10 @@ class Connection extends EventEmitter { */ connectTimeout() { const message = `Failed to connect to ${this.config.server}${this.config.options.port ? `:${this.config.options.port}` : `\\${this.config.options.instanceName}`} in ${this.config.options.connectTimeout}ms`; - this.debug.log(message); - this.emit('connect', new ConnectionError(message, 'ETIMEOUT')); + const customConnectorMessage = `Failed to connect using custom connector in ${this.config.options.connectTimeout}ms`; + const errMessage = this.config.options.connector ? customConnectorMessage : message; + this.debug.log(errMessage); + this.emit('connect', new ConnectionError(errMessage, 'ETIMEOUT')); this.connectTimer = undefined; this.dispatchEvent('connectTimeout'); } @@ -2202,8 +2219,10 @@ class Connection extends EventEmitter { socketError(error: Error) { if (this.state === this.STATE.CONNECTING || this.state === this.STATE.SENT_TLSSSLNEGOTIATION) { const message = `Failed to connect to ${this.config.server}:${this.config.options.port} - ${error.message}`; - this.debug.log(message); - this.emit('connect', new ConnectionError(message, 'ESOCKET')); + const customConnectorMessage = `Failed to connect using custom connector - ${error.message}`; + const errMessage = this.config.options.connector ? customConnectorMessage : message; + this.debug.log(errMessage); + this.emit('connect', new ConnectionError(errMessage, 'ESOCKET')); } else { const message = `Connection lost - ${error.message}`; this.debug.log(message); @@ -2228,15 +2247,21 @@ class Connection extends EventEmitter { * @private */ socketClose() { - this.debug.log('connection to ' + this.config.server + ':' + this.config.options.port + ' closed'); + const message = 'connection to ' + this.config.server + ':' + this.config.options.port + ' closed'; + const customConnectorMessage = 'connection closed'; + this.debug.log(this.config.options.connector ? customConnectorMessage : message); if (this.state === this.STATE.REROUTING) { - this.debug.log('Rerouting to ' + this.routingData!.server + ':' + this.routingData!.port); + const message = 'Rerouting to ' + this.routingData!.server + ':' + this.routingData!.port; + const customConnectorMessage = 'Rerouting'; + this.debug.log(this.config.options.connector ? customConnectorMessage : message); this.dispatchEvent('reconnect'); } else if (this.state === this.STATE.TRANSIENT_FAILURE_RETRY) { const server = this.routingData ? this.routingData.server : this.config.server; const port = this.routingData ? this.routingData.port : this.config.options.port; - this.debug.log('Retry after transient failure connecting to ' + server + ':' + port); + const message = 'Retry after transient failure connecting to ' + server + ':' + port; + const customConnectorMessage = 'Retry after transient failure connecting'; + this.debug.log(this.config.options.connector ? customConnectorMessage : message); this.dispatchEvent('retry'); } else { diff --git a/test/unit/custom-connector.js b/test/unit/custom-connector.js index 36e49d19d..94d9fde73 100644 --- a/test/unit/custom-connector.js +++ b/test/unit/custom-connector.js @@ -28,7 +28,6 @@ describe('custom connector', function() { const host = server.address().address; const port = server.address().port; const connection = new Connection({ - server: host, options: { connector: async () => { customConnectorCalled = true; @@ -37,7 +36,6 @@ describe('custom connector', function() { port, }); }, - port }, }); @@ -59,4 +57,104 @@ describe('custom connector', function() { connection.connect(); }); + + it('connection timeout using a custom connector', function(done) { + const host = server.address().address; + const port = server.address().port; + const connection = new Connection({ + options: { + connectTimeout: 10, + connector: async () => { + return net.connect({ + host, + port, + }); + }, + }, + }); + + // times out since no server response is defined + connection.connect((err) => { + assert.strictEqual( + err.code, + 'ETIMEOUT', + 'should emit timeout error code' + ); + assert.strictEqual( + err.message, + 'Failed to connect using custom connector in 10ms', + 'should emit expected custom connector timeout error msg' + ); + + done(); + }); + }); + + it('should emit socket error custom connector msg', function(done) { + const connection = new Connection({ + options: { + connector: async () => { + throw new Error('ERR'); + }, + }, + }); + + connection.connect((err) => { + assert.strictEqual( + err.code, + 'ESOCKET', + 'should emit expected error code' + ); + assert.strictEqual( + err.message, + 'Failed to connect using custom connector - ERR', + 'should emit expected custom connector error msg' + ); + done(); + }); + }); + + it('should only accept functions', function(done) { + assert.throws(() => { + new Connection({ + options: { + connector: 'foo', + }, + }); + }, Error, 'The "config.options.connector" property must be a function.'); + done(); + }); + + it('should not allow setting both server and connector options', function(done) { + assert.throws(() => { + new Connection({ + server: '0.0.0.0', + options: { + connector: async () => {}, + }, + }); + }, Error, 'Server and connector are mutually exclusive, but 0.0.0.0 and a connector function were provided'); + done(); + }); + + it('should not allow setting both port and connector options', function(done) { + assert.throws(() => { + new Connection({ + options: { + connector: async () => {}, + port: 8080, + }, + }); + }, Error, 'Port and connector are mutually exclusive, but 8080 and a connector function were provided'); + done(); + }); + + it('should require server config option if custom connector is undefined', function(done) { + assert.throws(() => { + new Connection({ + options: { port: 8080 }, + }); + }, TypeError, 'The "config.server" property is required and must be of type string.'); + done(); + }); });