node-firebird 2.16.1 → 2.16.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1434,6 +1434,49 @@ Firebird.attach(options, function (err, db) {
1434
1434
  });
1435
1435
  ```
1436
1436
 
1437
+ #### The auxiliary connection, and when `attachEvent` fails
1438
+
1439
+ Events do **not** travel over the connection you attached with. `db.attachEvent()` sends
1440
+ `op_connect_request`; the server opens a fresh listening socket, replies with its address and
1441
+ port, and the driver dials that address as a **second, independent TCP connection**. Only once
1442
+ that aux socket is up is the `FbEventManager` created and handed to your callback.
1443
+
1444
+ The aux port is chosen by the server and is *not* the port you connected to, so it has to be
1445
+ reachable from the client in its own right. Two deployments commonly get this wrong:
1446
+
1447
+ - **Containers.** A Firebird container that publishes only `3050` answers `op_connect_request`
1448
+ with an aux port nothing outside the container can reach. Pin it with `RemoteAuxPort` in
1449
+ `firebird.conf` and publish that port too, or run the client on the same network.
1450
+ - **Firewalls.** A rule that DROPs the aux port (rather than REJECTing it) leaves the dial
1451
+ waiting out the operating system's connect timeout — around two minutes on Linux — before
1452
+ the failure is reported.
1453
+
1454
+ If the server reports `0.0.0.0` or `::` as the aux address — usual when it listens on all
1455
+ interfaces — the driver dials the host from your connection options instead, so that host must
1456
+ be the one reaching the aux port.
1457
+
1458
+ Since **2.16.2** a failed dial is reported to the attachment callback as an `Error`, exactly
1459
+ once, carrying Node's socket `code`. Earlier versions recorded it internally and never called
1460
+ back, so `attachEvent()` and `attachEventAsync()` hung indefinitely:
1461
+
1462
+ ```js
1463
+ db.attachEvent(function (err, evtmgr) {
1464
+ if (err) {
1465
+ // ECONNREFUSED → aux port not reachable (unpublished container port, nothing listening)
1466
+ // ETIMEDOUT → aux port filtered by a firewall
1467
+ // EHOSTUNREACH → wrong host: the address the server reported, or your connection host
1468
+ // when the server reported 0.0.0.0/::
1469
+ console.error('event attachment failed:', err.code || err.message);
1470
+ return;
1471
+ }
1472
+ // ...
1473
+ });
1474
+ ```
1475
+
1476
+ Errors on the aux socket *after* it connects are not delivered to this callback. Poll
1477
+ `evtmgr.getState().isEventConnectionOpen` if you need to detect an aux connection that dies
1478
+ mid-subscription.
1479
+
1437
1480
  ### Escaping Query values
1438
1481
 
1439
1482
  ```js
@@ -2429,6 +2472,21 @@ If the failure is intermittent — the same code with the same credentials succe
2429
2472
 
2430
2473
  If the failure is consistent, the credentials really don't match an account for the authentication plugin in use: check `AuthServer`/`UserManager` in `firebird.conf` and remember that SRP and Legacy user managers keep separate password stores — a user created under one plugin does not automatically exist for the other.
2431
2474
 
2475
+ #### `db.attachEvent()` never calls back, or hangs, in Docker or behind a firewall
2476
+
2477
+ Upgrade to **2.16.2 or later**: before that release the driver recorded a failure to open the
2478
+ auxiliary event connection internally and never invoked the attachment callback, so
2479
+ `attachEvent()` silently stalled and `attachEventAsync()` returned a promise that never settled.
2480
+ From 2.16.2 the failure reaches your callback as an `Error` carrying Node's socket `code`.
2481
+
2482
+ The failure itself is a reachability problem, not a driver bug, and upgrading only makes it
2483
+ visible. Firebird events use a **second TCP connection** to a port the *server* chooses, which
2484
+ is not the port you connected to, so publishing `3050` alone is not enough: pin `RemoteAuxPort`
2485
+ in `firebird.conf` and publish that port too. See
2486
+ [§ The auxiliary connection](#the-auxiliary-connection-and-when-attachevent-fails) for the full
2487
+ picture, including the `0.0.0.0`/`::` case and why a DROP firewall rule delays the error by
2488
+ roughly two minutes.
2489
+
2432
2490
  ## Contributing
2433
2491
 
2434
2492
  Contributions are welcome — code, documentation, and bug reports alike.
@@ -9,8 +9,9 @@ declare class EventConnection {
9
9
  _xdr?: XdrReader;
10
10
  error: any;
11
11
  eventcallback: ((err: any, ret?: any) => void) | null;
12
- constructor(host: string, port: number, callback: (() => void) | undefined, db: any);
13
- _bind_events(host: string, port: number, callback?: () => void): void;
12
+ _connectSettled: boolean;
13
+ constructor(host: string, port: number, callback: ((err?: Error) => void) | undefined, db: any);
14
+ _bind_events(host: string, port: number, callback?: (err?: Error) => void): void;
14
15
  throwClosed(callback?: (err: any) => void): this;
15
16
  }
16
17
  export = EventConnection;
@@ -13,6 +13,7 @@ class EventConnection {
13
13
  this.emgr = null;
14
14
  this._isClosed = false;
15
15
  this._isOpened = false;
16
+ this._connectSettled = false;
16
17
  this._socket = net_1.default.createConnection(port, host);
17
18
  this._bind_events(host, port, callback);
18
19
  this.error = null;
@@ -20,17 +21,31 @@ class EventConnection {
20
21
  }
21
22
  _bind_events(host, port, callback) {
22
23
  var self = this;
24
+ function finishConnect(err) {
25
+ if (self._connectSettled)
26
+ return;
27
+ self._connectSettled = true;
28
+ if (callback)
29
+ callback(err);
30
+ }
23
31
  self._socket.on('close', function () {
24
32
  self._isClosed = true;
33
+ if (!self._isOpened) {
34
+ finishConnect(self.error || new Error(`Event connection to ${host}:${port} closed before connecting.`));
35
+ }
25
36
  });
26
37
  self._socket.on('error', function (e) {
27
38
  self.error = e;
39
+ if (!self._isOpened) {
40
+ if (!self._socket.destroyed)
41
+ self._socket.destroy();
42
+ finishConnect(e);
43
+ }
28
44
  });
29
45
  self._socket.on('connect', function () {
30
46
  self._isClosed = false;
31
47
  self._isOpened = true;
32
- if (callback)
33
- callback();
48
+ finishConnect();
34
49
  });
35
50
  self._socket.on('data', function (data) {
36
51
  var xdr, buf;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-firebird",
3
- "version": "2.16.1",
3
+ "version": "2.16.2",
4
4
  "description": "Pure JavaScript and Asynchronous Firebird client for Node.js.",
5
5
  "keywords": [
6
6
  "firebird",
@@ -13,37 +13,49 @@ class EventConnection {
13
13
  _xdr?: XdrReader;
14
14
  error: any;
15
15
  eventcallback: ((err: any, ret?: any) => void) | null;
16
+ _connectSettled: boolean;
16
17
 
17
- constructor(host: string, port: number, callback: (() => void) | undefined, db: any) {
18
+ constructor(host: string, port: number, callback: ((err?: Error) => void) | undefined, db: any) {
18
19
  var self = this;
19
20
  this.db = db;
20
21
  this.emgr = null;
21
22
  this._isClosed = false;
22
23
  this._isOpened = false;
24
+ this._connectSettled = false;
23
25
  this._socket = net.createConnection(port, host);
24
26
  this._bind_events(host, port, callback);
25
27
  this.error = null;
26
28
  this.eventcallback = null;
27
29
  }
28
30
 
29
- _bind_events(host: string, port: number, callback?: () => void): void {
31
+ _bind_events(host: string, port: number, callback?: (err?: Error) => void): void {
30
32
  var self = this;
31
33
 
32
- self._socket.on('close', function () {
34
+ function finishConnect(err?: Error) {
35
+ if (self._connectSettled) return;
36
+ self._connectSettled = true;
37
+ if (callback) callback(err);
38
+ }
33
39
 
40
+ self._socket.on('close', function () {
34
41
  self._isClosed = true;
42
+ if (!self._isOpened) {
43
+ finishConnect(self.error || new Error(`Event connection to ${host}:${port} closed before connecting.`));
44
+ }
35
45
  })
36
46
 
37
47
  self._socket.on('error', function (e) {
38
-
39
48
  self.error = e;
49
+ if (!self._isOpened) {
50
+ if (!self._socket.destroyed) self._socket.destroy();
51
+ finishConnect(e);
52
+ }
40
53
  })
41
54
 
42
55
  self._socket.on('connect', function () {
43
56
  self._isClosed = false;
44
57
  self._isOpened = true;
45
- if (callback)
46
- callback();
58
+ finishConnect();
47
59
  });
48
60
 
49
61
  self._socket.on('data', function (data: Buffer) {