UNPKG

@cloudamqp/amqp-client

Version:

AMQP 0-9-1 client, both for browsers (WebSocket) and node (TCP Socket)

642 lines 34.7 kB
"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.AMQPBaseClient = exports.MIN_FRAME_SIZE = exports.VERSION = void 0; const amqp_channel_js_1 = require("./amqp-channel.js"); const amqp_error_js_1 = require("./amqp-error.js"); const AMQPFrame = __importStar(require("./amqp-frame.js")); const amqp_message_js_1 = require("./amqp-message.js"); exports.VERSION = "4.0.0"; exports.MIN_FRAME_SIZE = 8192; class AMQPBaseClient { constructor(vhost, username, password, name, platform, frameMax = exports.MIN_FRAME_SIZE, heartbeat = 0, channelMax = 0, logger) { this.closed = true; this.channelMax = 0; this.textEncoder = new TextEncoder(); this.bufferPool = []; this.vhost = vhost; this.username = username; this.password = ""; Object.defineProperty(this, "password", { value: password, enumerable: false, }); if (name) this.name = name; if (platform) this.platform = platform; this.logger = logger || undefined; this.channels = [new amqp_channel_js_1.AMQPChannel(this, 0)]; this.onerror = (error) => { this.logger?.error("amqp-client connection closed", error.message); }; if (frameMax < exports.MIN_FRAME_SIZE) throw new Error(`frameMax must be ${exports.MIN_FRAME_SIZE} or larger`); this.frameMax = frameMax; if (heartbeat < 0) throw new Error("heartbeat must be positive"); this.heartbeat = heartbeat; if (channelMax && channelMax < 0) throw new Error("channelMax must be positive"); this.channelMax = channelMax; } channel(id) { if (this.closed) return this.rejectClosed(); if (id && id > 0) { const channel = this.channels[id]; if (channel) return Promise.resolve(channel); } if (!id) id = this.channels.findIndex((ch) => ch === undefined); if (id === -1) id = this.channels.length; if (id > this.channelMax && this.channelMax > 0) return Promise.reject(new amqp_error_js_1.AMQPError("Max number of channels reached", this)); const channel = new amqp_channel_js_1.AMQPChannel(this, id); this.channels[id] = channel; return channel.open(); } close(reason = "", code = 200) { if (this.closed) return this.rejectClosed(); this.closed = true; const frame = new AMQPFrame.Writer({ bufferSize: 512, type: AMQPFrame.Type.METHOD, channel: 0, classId: AMQPFrame.ClassId.CONNECTION, method: AMQPFrame.ConnectionMethod.CLOSE, }); frame.writeUint16(code); frame.writeShortString(reason); frame.writeUint16(0); frame.writeUint16(0); frame.finalize(); return new Promise((resolve, reject) => { this.send(frame.toUint8Array()) .then(() => (this.closePromise = [resolve, reject])) .catch(reject); }); } updateSecret(newSecret, reason) { const frame = new AMQPFrame.Writer({ bufferSize: 8192, type: AMQPFrame.Type.METHOD, channel: 0, classId: AMQPFrame.ClassId.CONNECTION, method: AMQPFrame.ConnectionMethod.UPDATE_SECRET, }); frame.writeLongString(newSecret); frame.writeShortString(reason); frame.finalize(); return new Promise((resolve, reject) => { this.send(frame.toUint8Array()) .then(() => (this.onUpdateSecretOk = resolve)) .catch(reject); }); } rejectClosed() { return Promise.reject(new amqp_error_js_1.AMQPError("Connection closed", this)); } rejectConnect(err) { if (this.connectPromise) { const [, reject] = this.connectPromise; delete this.connectPromise; reject(err); } this.closed = true; this.closeSocket(); } parseFrames(view) { for (let i = 0; i < view.byteLength;) { const type = view.getUint8(i); i += 1; const channelId = view.getUint16(i); i += 2; const frameSize = view.getUint32(i); i += 4; let frameEnd = 0; try { frameEnd = view.getUint8(i + frameSize); } catch { throw new amqp_error_js_1.AMQPError(`Frame end out of range, frameSize=${frameSize}, pos=${i}, byteLength=${view.byteLength}`, this); } if (frameEnd !== AMQPFrame.End.CODE) throw new amqp_error_js_1.AMQPError(`Invalid frame end ${frameEnd}, expected ${AMQPFrame.End.CODE}`, this); const channel = this.channels[channelId]; if (!channel) { this.logger?.warn("AMQP channel", channelId, "not open"); i += frameSize + 1; continue; } switch (type) { case AMQPFrame.Type.METHOD: { const classId = view.getUint16(i); i += 2; const methodId = view.getUint16(i); i += 2; switch (classId) { case AMQPFrame.ClassId.CONNECTION: { switch (methodId) { case AMQPFrame.ConnectionMethod.START: { i += frameSize - 4; const startOk = new AMQPFrame.Writer({ bufferSize: 8192, type: AMQPFrame.Type.METHOD, channel: 0, classId: AMQPFrame.ClassId.CONNECTION, method: AMQPFrame.ConnectionMethod.START_OK, }); const clientProps = { connection_name: this.name || undefined, product: "amqp-client.js", information: "https://github.com/cloudamqp/amqp-client.js", version: exports.VERSION, platform: this.platform, capabilities: { authentication_failure_close: true, "basic.nack": true, "connection.blocked": true, consumer_cancel_notify: true, exchange_exchange_bindings: true, per_consumer_qos: true, publisher_confirms: true, }, }; startOk.writeTable(clientProps); startOk.writeShortString("PLAIN"); const response = `\u0000${this.username}\u0000${this.password}`; startOk.writeLongString(response); startOk.writeShortString(""); startOk.finalize(); this.send(startOk.toUint8Array()).catch(this.rejectConnect); break; } case AMQPFrame.ConnectionMethod.TUNE: { const channelMax = view.getUint16(i); i += 2; const frameMax = view.getUint32(i); i += 4; const heartbeat = view.getUint16(i); i += 2; this.channelMax = this.channelMax === 0 ? channelMax : Math.min(this.channelMax, channelMax); this.frameMax = this.frameMax === 0 ? frameMax : Math.min(this.frameMax, frameMax); this.heartbeat = this.heartbeat === 0 ? 0 : Math.min(this.heartbeat, heartbeat); const tuneOk = new AMQPFrame.Writer({ bufferSize: 20, type: AMQPFrame.Type.METHOD, channel: 0, frameSize: 12, classId: AMQPFrame.ClassId.CONNECTION, method: AMQPFrame.ConnectionMethod.TUNE_OK, }); tuneOk.writeUint16(this.channelMax); tuneOk.writeUint32(this.frameMax); tuneOk.writeUint16(this.heartbeat); tuneOk.finalize(); this.send(tuneOk.toUint8Array()).catch(this.rejectConnect); const open = new AMQPFrame.Writer({ bufferSize: 512, type: AMQPFrame.Type.METHOD, channel: 0, classId: AMQPFrame.ClassId.CONNECTION, method: AMQPFrame.ConnectionMethod.OPEN, }); open.writeShortString(this.vhost); open.writeUint8(0); open.writeUint8(0); open.finalize(); this.send(open.toUint8Array()).catch(this.rejectConnect); break; } case AMQPFrame.ConnectionMethod.OPEN_OK: { i += 1; this.closed = false; const promise = this.connectPromise; if (promise) { const [resolve] = promise; delete this.connectPromise; resolve(this); } break; } case AMQPFrame.ConnectionMethod.CLOSE: { const code = view.getUint16(i); i += 2; const [text, strLen] = view.getShortString(i); i += strLen; const classId = view.getUint16(i); i += 2; const methodId = view.getUint16(i); i += 2; this.logger?.debug("connection closed by server", code, text, classId, methodId); const msg = `connection closed: ${text} (${code})`; const err = new amqp_error_js_1.AMQPError(msg, this); this.channels.forEach((ch) => ch.setClosed(err)); this.channels = [new amqp_channel_js_1.AMQPChannel(this, 0)]; const closeOk = new AMQPFrame.Writer({ bufferSize: 12, type: AMQPFrame.Type.METHOD, channel: 0, frameSize: 4, classId: AMQPFrame.ClassId.CONNECTION, method: AMQPFrame.ConnectionMethod.CLOSE_OK, }); closeOk.finalize(); this.send(closeOk.toUint8Array()).catch((err) => this.logger?.warn("Error while sending Connection#CloseOk", err)); this.onerror(err); this.ondisconnect?.(err); this.rejectConnect(err); this.onUpdateSecretOk?.(); break; } case AMQPFrame.ConnectionMethod.CLOSE_OK: { this.channels.forEach((ch) => ch.setClosed()); this.channels = [new amqp_channel_js_1.AMQPChannel(this, 0)]; const promise = this.closePromise; if (promise) { const [resolve] = promise; delete this.closePromise; resolve(); this.closeSocket(); } break; } case AMQPFrame.ConnectionMethod.BLOCKED: { const [reason, len] = view.getShortString(i); i += len; this.logger?.warn("AMQP connection blocked:", reason); this.blocked = reason; this.onblocked?.(reason); break; } case AMQPFrame.ConnectionMethod.UNBLOCKED: { this.logger?.info("AMQP connection unblocked"); delete this.blocked; this.onunblocked?.(); break; } case AMQPFrame.ConnectionMethod.UPDATE_SECRET_OK: { this.logger?.info("AMQP connection update secret ok"); this.onUpdateSecretOk?.(); delete this.onUpdateSecretOk; break; } default: i += frameSize - 4; this.logger?.error("unsupported class/method id", classId, methodId); } break; } case AMQPFrame.ClassId.CHANNEL: { switch (methodId) { case AMQPFrame.ChannelMethod.OPEN_OK: { i += 4; channel.resolveRPC(channel); break; } case AMQPFrame.ChannelMethod.FLOW_OK: { const active = view.getUint8(i) !== 0; i += 1; channel.resolveRPC(active); break; } case AMQPFrame.ChannelMethod.CLOSE: { const code = view.getUint16(i); i += 2; const [text, strLen] = view.getShortString(i); i += strLen; const classId = view.getUint16(i); i += 2; const methodId = view.getUint16(i); i += 2; this.logger?.debug("channel", channelId, "closed", code, text, classId, methodId); const msg = `channel ${channelId} closed: ${text} (${code})`; const err = new amqp_error_js_1.AMQPError(msg, this); channel.setClosed(err); delete this.channels[channelId]; const closeOk = new AMQPFrame.Writer({ bufferSize: 12, type: AMQPFrame.Type.METHOD, channel: channelId, frameSize: 4, classId: AMQPFrame.ClassId.CHANNEL, method: AMQPFrame.ChannelMethod.CLOSE_OK, }); closeOk.finalize(); this.send(closeOk.toUint8Array()).catch((err) => this.logger?.error("Error while sending Channel#closeOk", err)); break; } case AMQPFrame.ChannelMethod.CLOSE_OK: { channel.setClosed(); delete this.channels[channelId]; channel.resolveRPC(); break; } default: i += frameSize - 4; this.logger?.error("unsupported class/method id", classId, methodId); } break; } case AMQPFrame.ClassId.EXCHANGE: { switch (methodId) { case AMQPFrame.ExchangeMethod.DECLARE_OK: case AMQPFrame.ExchangeMethod.DELETE_OK: case AMQPFrame.ExchangeMethod.BIND_OK: case AMQPFrame.ExchangeMethod.UNBIND_OK: { channel.resolveRPC(); break; } default: i += frameSize - 4; this.logger?.error("unsupported class/method id", classId, methodId); } break; } case AMQPFrame.ClassId.QUEUE: { switch (methodId) { case AMQPFrame.QueueMethod.DECLARE_OK: { const [name, strLen] = view.getShortString(i); i += strLen; const messageCount = view.getUint32(i); i += 4; const consumerCount = view.getUint32(i); i += 4; channel.resolveRPC({ name, messageCount, consumerCount }); break; } case AMQPFrame.QueueMethod.BIND_OK: { channel.resolveRPC(); break; } case AMQPFrame.QueueMethod.PURGE_OK: { const messageCount = view.getUint32(i); i += 4; channel.resolveRPC({ messageCount }); break; } case AMQPFrame.QueueMethod.DELETE_OK: { const messageCount = view.getUint32(i); i += 4; channel.resolveRPC({ messageCount }); break; } case AMQPFrame.QueueMethod.UNBIND_OK: { channel.resolveRPC(); break; } default: i += frameSize - 4; this.logger?.error("unsupported class/method id", classId, methodId); } break; } case AMQPFrame.ClassId.BASIC: { switch (methodId) { case AMQPFrame.BasicMethod.QOS_OK: { channel.resolveRPC(); break; } case AMQPFrame.BasicMethod.CONSUME_OK: { const [consumerTag, len] = view.getShortString(i); i += len; channel.resolveRPC(consumerTag); break; } case AMQPFrame.BasicMethod.CANCEL: { const [consumerTag, len] = view.getShortString(i); i += len; const noWait = view.getUint8(i) === 1; i += 1; const consumer = channel.consumers.get(consumerTag); if (consumer) { consumer.setClosed(new amqp_error_js_1.AMQPError("Consumer cancelled by the server", this)); channel.consumers.delete(consumerTag); } if (!noWait) { const frame = new AMQPFrame.Writer({ bufferSize: 512, type: AMQPFrame.Type.METHOD, channel: channel.id, classId: AMQPFrame.ClassId.BASIC, method: AMQPFrame.BasicMethod.CANCEL_OK, }); frame.writeShortString(consumerTag); frame.finalize(); this.send(frame.toUint8Array()); } break; } case AMQPFrame.BasicMethod.CANCEL_OK: { const [consumerTag, len] = view.getShortString(i); i += len; channel.resolveRPC(consumerTag); break; } case AMQPFrame.BasicMethod.RETURN: { const code = view.getUint16(i); i += 2; const [text, len] = view.getShortString(i); i += len; const [exchange, exchangeLen] = view.getShortString(i); i += exchangeLen; const [routingKey, routingKeyLen] = view.getShortString(i); i += routingKeyLen; const message = new amqp_message_js_1.AMQPMessage(channel); message.exchange = exchange; message.routingKey = routingKey; message.replyCode = code; message.replyText = text; channel.returned = message; break; } case AMQPFrame.BasicMethod.DELIVER: { const [consumerTag, consumerTagLen] = view.getShortString(i); i += consumerTagLen; const deliveryTag = view.getUint64(i); i += 8; const redelivered = view.getUint8(i) === 1; i += 1; const [exchange, exchangeLen] = view.getShortString(i); i += exchangeLen; const [routingKey, routingKeyLen] = view.getShortString(i); i += routingKeyLen; const message = new amqp_message_js_1.AMQPMessage(channel); message.consumerTag = consumerTag; message.deliveryTag = deliveryTag; message.exchange = exchange; message.routingKey = routingKey; message.redelivered = redelivered; channel.delivery = message; break; } case AMQPFrame.BasicMethod.GET_OK: { const deliveryTag = view.getUint64(i); i += 8; const redelivered = view.getUint8(i) === 1; i += 1; const [exchange, exchangeLen] = view.getShortString(i); i += exchangeLen; const [routingKey, routingKeyLen] = view.getShortString(i); i += routingKeyLen; const messageCount = view.getUint32(i); i += 4; const message = new amqp_message_js_1.AMQPMessage(channel); message.deliveryTag = deliveryTag; message.redelivered = redelivered; message.exchange = exchange; message.routingKey = routingKey; message.messageCount = messageCount; channel.getMessage = message; break; } case AMQPFrame.BasicMethod.GET_EMPTY: { const [, len] = view.getShortString(i); i += len; channel.resolveRPC(null); break; } case AMQPFrame.BasicMethod.ACK: { const deliveryTag = view.getUint64(i); i += 8; const multiple = view.getUint8(i) === 1; i += 1; channel.publishConfirmed(deliveryTag, multiple, false); break; } case AMQPFrame.BasicMethod.RECOVER_OK: { channel.resolveRPC(); break; } case AMQPFrame.BasicMethod.NACK: { const deliveryTag = view.getUint64(i); i += 8; const multiple = view.getUint8(i) === 1; i += 1; channel.publishConfirmed(deliveryTag, multiple, true); break; } default: i += frameSize - 4; this.logger?.error("unsupported class/method id", classId, methodId); } break; } case AMQPFrame.ClassId.CONFIRM: { switch (methodId) { case AMQPFrame.ConfirmMethod.SELECT_OK: { channel.confirmId = 1; channel.resolveRPC(); break; } default: i += frameSize - 4; this.logger?.error("unsupported class/method id", classId, methodId); } break; } case AMQPFrame.ClassId.TX: { switch (methodId) { case AMQPFrame.TxMethod.SELECT_OK: case AMQPFrame.TxMethod.COMMIT_OK: case AMQPFrame.TxMethod.ROLLBACK_OK: { channel.resolveRPC(); break; } default: i += frameSize - 4; this.logger?.error("unsupported class/method id", classId, methodId); } break; } default: i += frameSize - 2; this.logger?.error("unsupported class id", classId); } break; } case AMQPFrame.Type.HEADER: { i += 4; const bodySize = view.getUint64(i); i += 8; const [properties, propLen] = view.getProperties(i); i += propLen; const message = channel.delivery || channel.getMessage || channel.returned; if (message) { message.bodySize = bodySize; message.properties = properties; message._rawBytes = new Uint8Array(bodySize); if (bodySize === 0) channel.onMessageReady(message); } else { this.logger?.warn("Header frame but no message"); } break; } case AMQPFrame.Type.BODY: { const message = channel.delivery || channel.getMessage || channel.returned; if (message && message._rawBytes) { const bodyPart = new Uint8Array(view.buffer, view.byteOffset + i, frameSize); message._rawBytes.set(bodyPart, message.bodyPos); message.bodyPos += frameSize; i += frameSize; if (message.bodyPos === message.bodySize) channel.onMessageReady(message); } else { this.logger?.warn("Body frame but no message"); } break; } case AMQPFrame.Type.HEARTBEAT: { const heartbeat = new Uint8Array([AMQPFrame.Type.HEARTBEAT, 0, 0, 0, 0, 0, 0, AMQPFrame.End.CODE]); this.send(heartbeat).catch((err) => this.logger?.warn("Error while sending heartbeat", err)); break; } default: this.logger?.error("invalid frame type:", type); i += frameSize; } i += 1; } } } exports.AMQPBaseClient = AMQPBaseClient; //# sourceMappingURL=amqp-base-client.js.map