nats
Version:
Node.js client for NATS, a lightweight, high-performance cloud native messaging system
495 lines • 18.8 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.nodeResolveHost = exports.NodeTransport = void 0;
/*
* Copyright 2020-2024 The NATS Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const nats_base_client_1 = require("./nats-base-client");
const net_1 = require("net");
const util_1 = require("../nats-base-client/util");
const tls_1 = require("tls");
const { resolve } = require("path");
const { readFile, existsSync } = require("fs");
const dns = require("dns");
const VERSION = "2.24.0";
const LANG = "nats.js";
class NodeTransport {
constructor() {
this.yields = [];
this.signal = (0, nats_base_client_1.deferred)();
this.closedNotification = (0, nats_base_client_1.deferred)();
this.connected = false;
this.tlsName = "";
this.done = false;
this.lang = LANG;
this.version = VERSION;
}
connect(hp, options) {
return __awaiter(this, void 0, void 0, function* () {
this.tlsName = hp.tlsName;
this.options = options;
const { tls } = this.options;
const { handshakeFirst } = tls || {};
try {
if (handshakeFirst === true) {
this.socket = yield this.tlsFirst(hp);
}
else {
this.socket = yield this.dial(hp);
}
const info = yield this.peekInfo();
(0, nats_base_client_1.checkOptions)(info, options);
const { tls_required: tlsRequired, tls_available: tlsAvailable } = info;
const desired = tlsAvailable === true && options.tls !== null;
if (!handshakeFirst && (tlsRequired || desired)) {
this.socket = yield this.startTLS();
}
//@ts-ignore: this is possibly a TlsSocket
if (tlsRequired && this.socket.encrypted !== true) {
throw new nats_base_client_1.NatsError("tls", nats_base_client_1.ErrorCode.ServerOptionNotAvailable);
}
this.connected = true;
this.setupHandlers();
this.signal.resolve();
return Promise.resolve();
}
catch (err) {
if (!err) {
// this seems to be possible in Kubernetes
// where an error is thrown, but it is undefined
// when something like istio-init is booting up
err = nats_base_client_1.NatsError.errorForCode(nats_base_client_1.ErrorCode.ConnectionRefused, new Error("node provided an undefined error!"));
}
const { code } = err;
const perr = code === "ECONNREFUSED"
? nats_base_client_1.NatsError.errorForCode(nats_base_client_1.ErrorCode.ConnectionRefused, err)
: err;
if (this.socket) {
this.socket.destroy();
}
throw perr;
}
});
}
dial(hp) {
const d = (0, nats_base_client_1.deferred)();
let dialError;
const socket = (0, net_1.createConnection)(hp.port, hp.hostname, () => {
d.resolve(socket);
socket.removeAllListeners();
});
socket.on("error", (err) => {
dialError = err;
});
socket.on("close", () => {
socket.removeAllListeners();
d.reject(dialError);
});
socket.setNoDelay(true);
return d;
}
get isClosed() {
return this.done;
}
close(err) {
return this._closed(err, false);
}
peekInfo() {
const d = (0, nats_base_client_1.deferred)();
let peekError;
this.socket.on("data", (frame) => {
this.yields.push(frame);
const t = nats_base_client_1.DataBuffer.concat(...this.yields);
const pm = (0, nats_base_client_1.extractProtocolMessage)(t);
if (pm !== "") {
try {
const m = nats_base_client_1.INFO.exec(pm);
if (!m) {
throw new Error("unexpected response from server");
}
const info = JSON.parse(m[1]);
d.resolve(info);
}
catch (err) {
d.reject(err);
}
finally {
this.socket.removeAllListeners();
}
}
});
this.socket.on("error", (err) => {
peekError = err;
});
this.socket.on("close", () => {
this.socket.removeAllListeners();
d.reject(peekError);
});
return d;
}
loadFile(fn) {
if (!fn) {
return Promise.resolve();
}
const d = (0, nats_base_client_1.deferred)();
try {
fn = resolve(fn);
if (!existsSync(fn)) {
d.reject(new Error(`${fn} doesn't exist`));
}
readFile(fn, (err, data) => {
if (err) {
return d.reject(err);
}
d.resolve(data);
});
}
catch (err) {
d.reject(err);
}
return d;
}
loadClientCerts() {
return __awaiter(this, void 0, void 0, function* () {
const tlsOpts = {};
const { certFile, cert, caFile, ca, keyFile, key } = this.options.tls;
try {
if (certFile) {
const data = yield this.loadFile(certFile);
if (data) {
tlsOpts.cert = data;
}
}
else if (cert) {
tlsOpts.cert = cert;
}
if (keyFile) {
const data = yield this.loadFile(keyFile);
if (data) {
tlsOpts.key = data;
}
}
else if (key) {
tlsOpts.key = key;
}
if (caFile) {
const data = yield this.loadFile(caFile);
if (data) {
tlsOpts.ca = [data];
}
}
else if (ca) {
tlsOpts.ca = ca;
}
return Promise.resolve(tlsOpts);
}
catch (err) {
return Promise.reject(err);
}
});
}
tlsFirst(hp) {
return __awaiter(this, void 0, void 0, function* () {
let tlsError;
let tlsOpts = {
servername: this.tlsName,
rejectUnauthorized: true,
};
if (this.socket) {
tlsOpts.socket = this.socket;
}
if (typeof this.options.tls === "object") {
try {
const certOpts = (yield this.loadClientCerts()) || {};
tlsOpts = (0, util_1.extend)(tlsOpts, this.options.tls, certOpts);
}
catch (err) {
return Promise.reject(new nats_base_client_1.NatsError(err.message, nats_base_client_1.ErrorCode.Tls, err));
}
}
const d = (0, nats_base_client_1.deferred)();
try {
const tlsSocket = (0, tls_1.connect)(hp.port, hp.hostname, tlsOpts, () => {
tlsSocket.removeAllListeners();
d.resolve(tlsSocket);
});
tlsSocket.on("error", (err) => {
tlsError = err;
});
tlsSocket.on("secureConnect", () => {
// socket won't be authorized, if the user disabled it
if (tlsOpts.rejectUnauthorized === false) {
return;
}
if (!tlsSocket.authorized) {
throw tlsSocket.authorizationError;
}
});
tlsSocket.on("close", () => {
d.reject(tlsError);
tlsSocket.removeAllListeners();
});
tlsSocket.setNoDelay(true);
}
catch (err) {
// tls throws errors on bad certs see nats.js#310
d.reject(nats_base_client_1.NatsError.errorForCode(nats_base_client_1.ErrorCode.Tls, err));
}
return d;
});
}
startTLS() {
return __awaiter(this, void 0, void 0, function* () {
let tlsError;
let tlsOpts = {
socket: this.socket,
servername: this.tlsName,
rejectUnauthorized: true,
};
if (typeof this.options.tls === "object") {
try {
const certOpts = (yield this.loadClientCerts()) || {};
tlsOpts = (0, util_1.extend)(tlsOpts, this.options.tls, certOpts);
}
catch (err) {
return Promise.reject(new nats_base_client_1.NatsError(err.message, nats_base_client_1.ErrorCode.Tls, err));
}
}
const d = (0, nats_base_client_1.deferred)();
try {
const tlsSocket = (0, tls_1.connect)(tlsOpts, () => {
tlsSocket.removeAllListeners();
d.resolve(tlsSocket);
});
tlsSocket.on("error", (err) => {
tlsError = err;
});
tlsSocket.on("secureConnect", () => {
// socket won't be authorized, if the user disabled it
if (tlsOpts.rejectUnauthorized === false) {
return;
}
if (!tlsSocket.authorized) {
throw tlsSocket.authorizationError;
}
});
tlsSocket.on("close", () => {
d.reject(tlsError);
tlsSocket.removeAllListeners();
});
}
catch (err) {
// tls throws errors on bad certs see nats.js#310
d.reject(nats_base_client_1.NatsError.errorForCode(nats_base_client_1.ErrorCode.Tls, err));
}
return d;
});
}
setupHandlers() {
let connError;
this.socket.on("data", (frame) => {
this.yields.push(frame);
return this.signal.resolve();
});
this.socket.on("error", (err) => {
connError = err;
});
this.socket.on("end", () => {
var _a, _b;
if ((_a = this.socket) === null || _a === void 0 ? void 0 : _a.destroyed) {
return;
}
(_b = this.socket) === null || _b === void 0 ? void 0 : _b.write(new Uint8Array(0), () => {
var _a;
(_a = this.socket) === null || _a === void 0 ? void 0 : _a.end();
});
});
this.socket.on("close", () => {
this._closed(connError, false);
});
}
[Symbol.asyncIterator]() {
return this.iterate();
}
iterate() {
return __asyncGenerator(this, arguments, function* iterate_1() {
const debug = this.options.debug;
while (true) {
if (this.yields.length === 0) {
yield __await(this.signal);
}
const yields = this.yields;
this.yields = [];
for (let i = 0; i < yields.length; i++) {
if (debug) {
console.info(`> ${(0, nats_base_client_1.render)(yields[i])}`);
}
yield yield __await(yields[i]);
}
// yielding could have paused and microtask
// could have added messages. Prevent allocations
// if possible
if (this.done) {
break;
}
else if (this.yields.length === 0) {
yields.length = 0;
this.yields = yields;
this.signal = (0, nats_base_client_1.deferred)();
}
}
});
}
discard() {
// ignored - this is not required, as there's no throttling
}
disconnect() {
this._closed(undefined, true).then().catch();
}
isEncrypted() {
return this.socket instanceof tls_1.TLSSocket;
}
_send(frame) {
if (this.isClosed || this.socket === undefined) {
return Promise.resolve();
}
if (this.options.debug) {
console.info(`< ${(0, nats_base_client_1.render)(frame)}`);
}
const d = (0, nats_base_client_1.deferred)();
try {
this.socket.write(frame, (err) => {
if (err) {
if (this.options.debug) {
console.error(`!!! ${(0, nats_base_client_1.render)(frame)}: ${err}`);
}
return d.reject(err);
}
return d.resolve();
});
}
catch (err) {
if (this.options.debug) {
console.error(`!!! ${(0, nats_base_client_1.render)(frame)}: ${err}`);
}
d.reject(err);
}
return d;
}
send(frame) {
const p = this._send(frame);
p.catch((_err) => {
// we ignore write errors because client will
// fail on a read or when the heartbeat timer
// detects a stale connection
});
}
_closed(err_1) {
return __awaiter(this, arguments, void 0, function* (err, internal = true) {
// if this connection didn't succeed, then ignore it.
if (!this.connected)
return;
if (this.done)
return;
this.closeError = err;
// only try to flush the outbound buffer if we got no error and
// the close is internal, if the transport closed, we are done.
if (!err && this.socket && internal) {
try {
yield this._send(new TextEncoder().encode(""));
}
catch (err) {
if (this.options.debug) {
console.log("transport close terminated with an error", err);
}
}
}
try {
if (this.socket) {
this.socket.removeAllListeners();
this.socket.destroy();
this.socket = undefined;
}
}
catch (err) {
console.log(err);
}
this.done = true;
this.closedNotification.resolve(this.closeError);
});
}
closed() {
return this.closedNotification;
}
}
exports.NodeTransport = NodeTransport;
function nodeResolveHost(s) {
return __awaiter(this, void 0, void 0, function* () {
const a = (0, nats_base_client_1.deferred)();
const aaaa = (0, nats_base_client_1.deferred)();
dns.resolve4(s, (err, records) => {
if (err) {
a.resolve(err);
}
else {
a.resolve(records);
}
});
dns.resolve6(s, (err, records) => {
if (err) {
aaaa.resolve(err);
}
else {
aaaa.resolve(records);
}
});
const ips = [];
const da = yield a;
if (Array.isArray(da)) {
ips.push(...da);
}
const daaaa = yield aaaa;
if (Array.isArray(daaaa)) {
ips.push(...daaaa);
}
if (ips.length === 0) {
ips.push(s);
}
return ips;
});
}
exports.nodeResolveHost = nodeResolveHost;
//# sourceMappingURL=node_transport.js.map