@cloudamqp/amqp-client
Version:
AMQP 0-9-1 client, both for browsers (WebSocket) and node (TCP Socket)
208 lines • 9.37 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.AMQPClient = void 0;
const amqp_base_client_js_1 = require("./amqp-base-client.js");
const amqp_channel_js_1 = require("./amqp-channel.js");
const amqp_error_js_1 = require("./amqp-error.js");
const amqp_view_js_1 = require("./amqp-view.js");
const buffer_1 = require("buffer");
const net = __importStar(require("net"));
const tls = __importStar(require("tls"));
class AMQPClient extends amqp_base_client_js_1.AMQPBaseClient {
constructor(url, tlsOptions, logger) {
const u = new URL(url);
const vhost = decodeURIComponent(u.pathname.slice(1)) || "/";
const username = decodeURIComponent(u.username) || "guest";
const password = decodeURIComponent(u.password) || "guest";
const name = u.searchParams.get("name") || "";
const frameMax = parseInt(u.searchParams.get("frameMax") || amqp_base_client_js_1.MIN_FRAME_SIZE.toString());
const heartbeat = parseInt(u.searchParams.get("heartbeat") || "0");
const channelMax = parseInt(u.searchParams.get("channelMax") || "0");
const platform = `${process.release.name} ${process.version} ${process.platform} ${process.arch}`;
super(vhost, username, password, name, platform, frameMax, heartbeat, channelMax, logger);
this.tls = u.protocol === "amqps:";
this.tlsOptions = tlsOptions;
this.host = u.hostname || "localhost";
this.port = parseInt(u.port) || (this.tls ? 5671 : 5672);
this.insecure = u.searchParams.get("insecure") !== null;
this.framePos = 0;
this.frameSize = 0;
this.frameBuffer = buffer_1.Buffer.allocUnsafe(frameMax);
Object.defineProperty(this, "frameBuffer", {
enumerable: false,
});
}
connect() {
this.framePos = 0;
this.frameSize = 0;
this.channels = [new amqp_channel_js_1.AMQPChannel(this, 0)];
let rejectConnection;
let socketError;
const socket = this.connectSocket();
socket.on("connect", () => {
socket.on("error", (err) => {
socketError = new amqp_error_js_1.AMQPError(err.message, this);
this.onerror(socketError);
});
socket.on("end", () => {
if (rejectConnection) {
rejectConnection(new amqp_error_js_1.AMQPError("Connection ended", this));
}
});
socket.on("close", (hadError) => {
const clientClosed = this.closed;
this.closed = true;
const closeError = hadError ? socketError : clientClosed ? undefined : new amqp_error_js_1.AMQPError("Socket closed", this);
if (!clientClosed) {
const err = closeError ?? new amqp_error_js_1.AMQPError("Socket closed", this);
this.channels.forEach((ch) => ch?.setClosed(err));
this.channels = [new amqp_channel_js_1.AMQPChannel(this, 0)];
}
if (closeError && !hadError) {
this.onerror(closeError);
}
if (!clientClosed) {
this.ondisconnect?.(closeError);
}
this.socket = undefined;
socketError = undefined;
});
});
Object.defineProperty(this, "socket", {
value: socket,
writable: true,
enumerable: false,
});
socket.setTimeout((this.heartbeat || 60) * 1000);
if (this.heartbeat === 0)
socket.setKeepAlive(true, 60);
return new Promise((resolve, reject) => {
rejectConnection = reject;
socket.on("timeout", () => reject(new amqp_error_js_1.AMQPError("timeout", this)));
socket.on("error", (err) => reject(new amqp_error_js_1.AMQPError(err.message, this)));
const onConnect = (conn) => {
socket.setTimeout(this.heartbeat * 1000);
socket.on("timeout", () => {
this.onerror(new amqp_error_js_1.AMQPError(`Heartbeat timeout`, this));
this.closeSocket();
});
resolve(conn);
};
this.connectPromise = [onConnect, reject];
});
}
connectSocket() {
const options = {
host: this.host,
port: this.port,
servername: net.isIP(this.host) ? "" : this.host,
rejectUnauthorized: !this.insecure,
...this.tlsOptions,
};
const sendStart = () => this.send(new Uint8Array([65, 77, 81, 80, 0, 0, 9, 1]));
const conn = this.tls ? tls.connect(options, sendStart) : net.connect(options, sendStart);
conn.on("data", this.onRead.bind(this));
return conn;
}
onRead(buf) {
const bufLen = buf.length;
let bufPos = 0;
while (bufPos < bufLen) {
if (this.frameSize === 0) {
if (this.framePos !== 0) {
const copied = buf.copy(this.frameBuffer, this.framePos, bufPos, bufPos + 7 - this.framePos);
if (copied === 0)
throw new amqp_error_js_1.AMQPError(`Copied 0 bytes framePos=${this.framePos} bufPos=${bufPos} bytesWritten=${bufLen}`, this);
this.frameSize = this.frameBuffer.readInt32BE(bufPos + 3) + 8;
this.framePos += copied;
bufPos += copied;
continue;
}
if (bufPos + 3 + 4 > bufLen) {
const copied = buf.copy(this.frameBuffer, this.framePos, bufPos, bufLen);
if (copied === 0)
throw new amqp_error_js_1.AMQPError(`Copied 0 bytes framePos=${this.framePos} bufPos=${bufPos} bytesWritten=${bufLen}`, this);
this.framePos += copied;
break;
}
this.frameSize = buf.readInt32BE(bufPos + 3) + 8;
if (bufLen - bufPos >= this.frameSize) {
const view = new amqp_view_js_1.AMQPView(buf.buffer, buf.byteOffset + bufPos, this.frameSize);
this.parseFrames(view);
bufPos += this.frameSize;
this.frameSize = 0;
continue;
}
}
const leftOfFrame = this.frameSize - this.framePos;
const copyBytes = Math.min(leftOfFrame, bufLen - bufPos);
const copied = buf.copy(this.frameBuffer, this.framePos, bufPos, bufPos + copyBytes);
if (copied === 0)
throw new amqp_error_js_1.AMQPError(`Copied 0 bytes, please report this bug, frameSize=${this.frameSize} framePos=${this.framePos} bufPos=${bufPos} copyBytes=${copyBytes} bytesWritten=${bufLen}`, this);
this.framePos += copied;
bufPos += copied;
if (this.framePos === this.frameSize) {
const view = new amqp_view_js_1.AMQPView(this.frameBuffer.buffer, 0, this.frameSize);
this.parseFrames(view);
this.frameSize = this.framePos = 0;
}
}
return true;
}
send(bytes) {
return new Promise((resolve, reject) => {
if (!this.socket)
return reject(new amqp_error_js_1.AMQPError("Socket not connected", this));
try {
this.socket.write(bytes, undefined, (err) => (err ? reject(err) : resolve()));
}
catch (err) {
this.closeSocket();
reject(err);
}
});
}
closeSocket() {
this.closed = true;
if (this.socket) {
this.socket.end();
this.socket.destroy();
}
this.socket = undefined;
}
}
exports.AMQPClient = AMQPClient;
//# sourceMappingURL=amqp-socket-client.js.map