node-firebird 2.16.1 → 2.17.0
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 +64 -0
- package/lib/types.d.ts +2 -0
- package/lib/utils.d.ts +5 -0
- package/lib/utils.js +9 -0
- package/lib/wire/database.js +1 -3
- package/lib/wire/eventConnection.d.ts +3 -2
- package/lib/wire/eventConnection.js +17 -2
- package/package.json +1 -1
- package/src/types.ts +2 -0
- package/src/utils.ts +7 -0
- package/src/wire/database.ts +2 -4
- package/src/wire/eventConnection.ts +18 -6
package/README.md
CHANGED
|
@@ -181,6 +181,7 @@ same way `PGUSER`/`PGPASSWORD` work with pg.
|
|
|
181
181
|
var options = {};
|
|
182
182
|
|
|
183
183
|
options.host = '127.0.0.1';
|
|
184
|
+
options.eventHost = undefined; // optional; override the server-advertised host for the auxiliary event connection
|
|
184
185
|
options.port = 3050;
|
|
185
186
|
options.database = 'database.fdb';
|
|
186
187
|
options.user = 'SYSDBA';
|
|
@@ -1434,6 +1435,54 @@ Firebird.attach(options, function (err, db) {
|
|
|
1434
1435
|
});
|
|
1435
1436
|
```
|
|
1436
1437
|
|
|
1438
|
+
#### The auxiliary connection, and when `attachEvent` fails
|
|
1439
|
+
|
|
1440
|
+
Events do **not** travel over the connection you attached with. `db.attachEvent()` sends
|
|
1441
|
+
`op_connect_request`; the server opens a fresh listening socket, replies with its address and
|
|
1442
|
+
port, and the driver dials that address as a **second, independent TCP connection**. Only once
|
|
1443
|
+
that aux socket is up is the `FbEventManager` created and handed to your callback.
|
|
1444
|
+
|
|
1445
|
+
The aux port is chosen by the server and is *not* the port you connected to, so it has to be
|
|
1446
|
+
reachable from the client in its own right. Two deployments commonly get this wrong:
|
|
1447
|
+
|
|
1448
|
+
- **Containers.** A Firebird container that publishes only `3050` answers `op_connect_request`
|
|
1449
|
+
with an aux port nothing outside the container can reach. Pin it with `RemoteAuxPort` in
|
|
1450
|
+
`firebird.conf` and publish that port too, or run the client on the same network.
|
|
1451
|
+
- **Firewalls.** A rule that DROPs the aux port (rather than REJECTing it) leaves the dial
|
|
1452
|
+
waiting out the operating system's connect timeout — around two minutes on Linux — before
|
|
1453
|
+
the failure is reported.
|
|
1454
|
+
|
|
1455
|
+
If the server reports `0.0.0.0` or `::` as the aux address — usual when it listens on all
|
|
1456
|
+
interfaces — the driver dials the host from your connection options instead, so that host must
|
|
1457
|
+
be the one reaching the aux port.
|
|
1458
|
+
|
|
1459
|
+
Behind NAT, a tunnel, container networking, or a load balancer, the advertised
|
|
1460
|
+
address may not be reachable from the client. Set `options.eventHost` to override
|
|
1461
|
+
only the host used for the auxiliary event connection. The auxiliary port is
|
|
1462
|
+
still selected by Firebird; use `RemoteAuxPort` when it also needs to be fixed.
|
|
1463
|
+
|
|
1464
|
+
Since **2.16.2** a failed dial is reported to the attachment callback as an `Error`, exactly
|
|
1465
|
+
once, carrying Node's socket `code`. Earlier versions recorded it internally and never called
|
|
1466
|
+
back, so `attachEvent()` and `attachEventAsync()` hung indefinitely:
|
|
1467
|
+
|
|
1468
|
+
```js
|
|
1469
|
+
db.attachEvent(function (err, evtmgr) {
|
|
1470
|
+
if (err) {
|
|
1471
|
+
// ECONNREFUSED → aux port not reachable (unpublished container port, nothing listening)
|
|
1472
|
+
// ETIMEDOUT → aux port filtered by a firewall
|
|
1473
|
+
// EHOSTUNREACH → wrong host: the address the server reported, or your connection host
|
|
1474
|
+
// when the server reported 0.0.0.0/::
|
|
1475
|
+
console.error('event attachment failed:', err.code || err.message);
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
// ...
|
|
1479
|
+
});
|
|
1480
|
+
```
|
|
1481
|
+
|
|
1482
|
+
Errors on the aux socket *after* it connects are not delivered to this callback. Poll
|
|
1483
|
+
`evtmgr.getState().isEventConnectionOpen` if you need to detect an aux connection that dies
|
|
1484
|
+
mid-subscription.
|
|
1485
|
+
|
|
1437
1486
|
### Escaping Query values
|
|
1438
1487
|
|
|
1439
1488
|
```js
|
|
@@ -2429,6 +2478,21 @@ If the failure is intermittent — the same code with the same credentials succe
|
|
|
2429
2478
|
|
|
2430
2479
|
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
2480
|
|
|
2481
|
+
#### `db.attachEvent()` never calls back, or hangs, in Docker or behind a firewall
|
|
2482
|
+
|
|
2483
|
+
Upgrade to **2.16.2 or later**: before that release the driver recorded a failure to open the
|
|
2484
|
+
auxiliary event connection internally and never invoked the attachment callback, so
|
|
2485
|
+
`attachEvent()` silently stalled and `attachEventAsync()` returned a promise that never settled.
|
|
2486
|
+
From 2.16.2 the failure reaches your callback as an `Error` carrying Node's socket `code`.
|
|
2487
|
+
|
|
2488
|
+
The failure itself is a reachability problem, not a driver bug, and upgrading only makes it
|
|
2489
|
+
visible. Firebird events use a **second TCP connection** to a port the *server* chooses, which
|
|
2490
|
+
is not the port you connected to, so publishing `3050` alone is not enough: pin `RemoteAuxPort`
|
|
2491
|
+
in `firebird.conf` and publish that port too. See
|
|
2492
|
+
[§ The auxiliary connection](#the-auxiliary-connection-and-when-attachevent-fails) for the full
|
|
2493
|
+
picture, including the `0.0.0.0`/`::` case and why a DROP firewall rule delays the error by
|
|
2494
|
+
roughly two minutes.
|
|
2495
|
+
|
|
2432
2496
|
## Contributing
|
|
2433
2497
|
|
|
2434
2498
|
Contributions are welcome — code, documentation, and bug reports alike.
|
package/lib/types.d.ts
CHANGED
|
@@ -352,6 +352,8 @@ export interface Statement {
|
|
|
352
352
|
export type SupportedCharacterSet = 'NONE' | 'CP943C' | 'DOS737' | 'DOS775' | 'DOS858' | 'DOS862' | 'DOS864' | 'DOS866' | 'DOS869' | 'GB18030' | 'GBK' | 'ISO8859_1' | 'ISO8859_2' | 'ISO8859_3' | 'ISO8859_4' | 'ISO8859_5' | 'ISO8859_6' | 'ISO8859_7' | 'ISO8859_8' | 'ISO8859_9' | 'ISO8859_13' | 'KOI8R' | 'KOI8U' | 'TIS620' | 'UTF8' | 'WIN1251' | 'WIN1252' | 'WIN1253' | 'WIN1254' | 'WIN1255' | 'WIN1256' | 'WIN1257' | 'WIN1258' | 'WIN_1258';
|
|
353
353
|
export interface Options {
|
|
354
354
|
host?: string;
|
|
355
|
+
/** Override the server-advertised auxiliary event host (for NAT, tunnels and load balancers). */
|
|
356
|
+
eventHost?: string;
|
|
355
357
|
port?: number;
|
|
356
358
|
database?: string;
|
|
357
359
|
user?: string;
|
package/lib/utils.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { FbStatusItem } from './callback';
|
|
2
|
+
/** Resolve the address used for Firebird's auxiliary event connection. */
|
|
3
|
+
export declare function resolveEventHost(options: {
|
|
4
|
+
host?: string;
|
|
5
|
+
eventHost?: string;
|
|
6
|
+
}, advertisedHost: string): string;
|
|
2
7
|
/**
|
|
3
8
|
* Parse date from string
|
|
4
9
|
*/
|
package/lib/utils.js
CHANGED
|
@@ -4,10 +4,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.escape = exports.lookupMessages = exports.batchResultToError = exports.parseDate = void 0;
|
|
7
|
+
exports.resolveEventHost = resolveEventHost;
|
|
7
8
|
exports.noop = noop;
|
|
8
9
|
const firebird_msg_json_1 = __importDefault(require("./firebird.msg.json"));
|
|
9
10
|
const const_1 = __importDefault(require("./wire/const"));
|
|
10
11
|
const MessagesError = firebird_msg_json_1.default;
|
|
12
|
+
/** Resolve the address used for Firebird's auxiliary event connection. */
|
|
13
|
+
function resolveEventHost(options, advertisedHost) {
|
|
14
|
+
if (options.eventHost)
|
|
15
|
+
return options.eventHost;
|
|
16
|
+
if (advertisedHost === '0.0.0.0' || advertisedHost === '::')
|
|
17
|
+
return options.host || const_1.default.DEFAULT_HOST;
|
|
18
|
+
return advertisedHost;
|
|
19
|
+
}
|
|
11
20
|
/**
|
|
12
21
|
* Parse date from string
|
|
13
22
|
*/
|
package/lib/wire/database.js
CHANGED
|
@@ -404,9 +404,7 @@ class Database extends events_1.default.EventEmitter {
|
|
|
404
404
|
if (process.env.FIREBIRD_DEBUG) {
|
|
405
405
|
console.log('[fb-debug] Database.attachEvent: auxConnection ok, connecting to aux port %s:%d', socket_info.host, socket_info.port);
|
|
406
406
|
}
|
|
407
|
-
const host = (
|
|
408
|
-
? self.connection.options.host
|
|
409
|
-
: socket_info.host;
|
|
407
|
+
const host = (0, utils_1.resolveEventHost)(self.connection.options, socket_info.host);
|
|
410
408
|
const eventConnection = new eventConnection_1.default(host, socket_info.port, function (err) {
|
|
411
409
|
if (err) {
|
|
412
410
|
if (process.env.FIREBIRD_DEBUG) {
|
|
@@ -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
|
-
|
|
13
|
-
|
|
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
|
-
|
|
33
|
-
callback();
|
|
48
|
+
finishConnect();
|
|
34
49
|
});
|
|
35
50
|
self._socket.on('data', function (data) {
|
|
36
51
|
var xdr, buf;
|
package/package.json
CHANGED
package/src/types.ts
CHANGED
|
@@ -413,6 +413,8 @@ export type SupportedCharacterSet = |
|
|
|
413
413
|
|
|
414
414
|
export interface Options {
|
|
415
415
|
host?: string;
|
|
416
|
+
/** Override the server-advertised auxiliary event host (for NAT, tunnels and load balancers). */
|
|
417
|
+
eventHost?: string;
|
|
416
418
|
port?: number;
|
|
417
419
|
database?: string;
|
|
418
420
|
user?: string;
|
package/src/utils.ts
CHANGED
|
@@ -4,6 +4,13 @@ import type { FbStatusItem } from './callback';
|
|
|
4
4
|
|
|
5
5
|
const MessagesError = messagesJson as Record<string, string>;
|
|
6
6
|
|
|
7
|
+
/** Resolve the address used for Firebird's auxiliary event connection. */
|
|
8
|
+
export function resolveEventHost(options: { host?: string; eventHost?: string }, advertisedHost: string): string {
|
|
9
|
+
if (options.eventHost) return options.eventHost;
|
|
10
|
+
if (advertisedHost === '0.0.0.0' || advertisedHost === '::') return options.host || Const.DEFAULT_HOST;
|
|
11
|
+
return advertisedHost;
|
|
12
|
+
}
|
|
13
|
+
|
|
7
14
|
/**
|
|
8
15
|
* Parse date from string
|
|
9
16
|
*/
|
package/src/wire/database.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Events from 'events';
|
|
2
2
|
import { doError, fromCallback, type Callback, type SimpleCallback } from '../callback';
|
|
3
|
-
import { batchResultToError, escape } from '../utils';
|
|
3
|
+
import { batchResultToError, escape, resolveEventHost } from '../utils';
|
|
4
4
|
import Const from './const';
|
|
5
5
|
import { makeSqlTag, type SqlTag } from '../sql-template';
|
|
6
6
|
import { computeColumnKeys, nestCell, resolveKeyTransform, resolveNestTables } from './xsqlvar';
|
|
@@ -481,9 +481,7 @@ class Database extends Events.EventEmitter {
|
|
|
481
481
|
console.log('[fb-debug] Database.attachEvent: auxConnection ok, connecting to aux port %s:%d', socket_info.host, socket_info.port);
|
|
482
482
|
}
|
|
483
483
|
|
|
484
|
-
const host = (
|
|
485
|
-
? self.connection.options.host
|
|
486
|
-
: socket_info.host;
|
|
484
|
+
const host = resolveEventHost(self.connection.options, socket_info.host);
|
|
487
485
|
|
|
488
486
|
const eventConnection = new EventConnection(
|
|
489
487
|
host, socket_info.port, function(err?: any) {
|
|
@@ -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
|
-
|
|
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
|
-
|
|
46
|
-
callback();
|
|
58
|
+
finishConnect();
|
|
47
59
|
});
|
|
48
60
|
|
|
49
61
|
self._socket.on('data', function (data: Buffer) {
|