UNPKG

@cloudamqp/amqp-client

Version:

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

1,451 lines (1,445 loc) 122 kB
class AMQPError extends Error { constructor(message, connection) { super(message); this.name = "AMQPError"; this.connection = connection; } } class AMQPView extends DataView { getUint64(byteOffset, littleEndian) { const left = this.getUint32(byteOffset, littleEndian); const right = this.getUint32(byteOffset + 4, littleEndian); const combined = littleEndian ? left + 2 ** 32 * right : 2 ** 32 * left + right; if (!Number.isSafeInteger(combined)) { console.warn(combined, "exceeds MAX_SAFE_INTEGER. Precision may be lost"); } return combined; } setUint64(byteOffset, value, littleEndian) { this.setBigUint64(byteOffset, BigInt(value), littleEndian); } getInt64(byteOffset, littleEndian) { return Number(this.getBigInt64(byteOffset, littleEndian)); } setInt64(byteOffset, value, littleEndian) { this.setBigInt64(byteOffset, BigInt(value), littleEndian); } getShortString(byteOffset) { const len = this.getUint8(byteOffset); byteOffset += 1; if (typeof Buffer !== "undefined") { const text = Buffer.from(this.buffer, this.byteOffset + byteOffset, len).toString(); return [text, len + 1]; } else { const view = new Uint8Array(this.buffer, this.byteOffset + byteOffset, len); const text = new TextDecoder().decode(view); return [text, len + 1]; } } setShortString(byteOffset, string) { if (typeof Buffer !== "undefined") { const len = Buffer.byteLength(string); if (len > 255) throw new Error(`Short string too long, ${len} bytes: ${string.substring(0, 255)}...`); this.setUint8(byteOffset, len); byteOffset += 1; Buffer.from(this.buffer, this.byteOffset + byteOffset, len).write(string); return len + 1; } else { const utf8 = new TextEncoder().encode(string); const len = utf8.byteLength; if (len > 255) throw new Error(`Short string too long, ${len} bytes: ${string.substring(0, 255)}...`); this.setUint8(byteOffset, len); byteOffset += 1; const view = new Uint8Array(this.buffer, this.byteOffset + byteOffset); view.set(utf8); return len + 1; } } getLongString(byteOffset, littleEndian) { const len = this.getUint32(byteOffset, littleEndian); byteOffset += 4; if (typeof Buffer !== "undefined") { const text = Buffer.from(this.buffer, this.byteOffset + byteOffset, len).toString(); return [text, len + 4]; } else { const view = new Uint8Array(this.buffer, this.byteOffset + byteOffset, len); const text = new TextDecoder().decode(view); return [text, len + 4]; } } setLongString(byteOffset, string, littleEndian) { if (typeof Buffer !== "undefined") { const len = Buffer.byteLength(string); this.setUint32(byteOffset, len, littleEndian); byteOffset += 4; Buffer.from(this.buffer, this.byteOffset + byteOffset, len).write(string); return len + 4; } else { const utf8 = new TextEncoder().encode(string); const len = utf8.byteLength; this.setUint32(byteOffset, len, littleEndian); byteOffset += 4; const view = new Uint8Array(this.buffer, this.byteOffset + byteOffset); view.set(utf8); return len + 4; } } getProperties(byteOffset, littleEndian) { let j = byteOffset; const flags = this.getUint16(j, littleEndian); j += 2; const props = {}; if ((flags & 0x8000) > 0) { const [contentType, len] = this.getShortString(j); j += len; props.contentType = contentType; } if ((flags & 0x4000) > 0) { const [contentEncoding, len] = this.getShortString(j); j += len; props.contentEncoding = contentEncoding; } if ((flags & 0x2000) > 0) { const [headers, len] = this.getTable(j, littleEndian); j += len; props.headers = headers; } if ((flags & 0x1000) > 0) { props.deliveryMode = this.getUint8(j); j += 1; } if ((flags & 0x0800) > 0) { props.priority = this.getUint8(j); j += 1; } if ((flags & 0x0400) > 0) { const [correlationId, len] = this.getShortString(j); j += len; props.correlationId = correlationId; } if ((flags & 0x0200) > 0) { const [replyTo, len] = this.getShortString(j); j += len; props.replyTo = replyTo; } if ((flags & 0x0100) > 0) { const [expiration, len] = this.getShortString(j); j += len; props.expiration = expiration; } if ((flags & 0x0080) > 0) { const [messageId, len] = this.getShortString(j); j += len; props.messageId = messageId; } if ((flags & 0x0040) > 0) { props.timestamp = new Date(this.getInt64(j, littleEndian) * 1000); j += 8; } if ((flags & 0x0020) > 0) { const [type, len] = this.getShortString(j); j += len; props.type = type; } if ((flags & 0x0010) > 0) { const [userId, len] = this.getShortString(j); j += len; props.userId = userId; } if ((flags & 0x0008) > 0) { const [appId, len] = this.getShortString(j); j += len; props.appId = appId; } const len = j - byteOffset; return [props, len]; } setProperties(byteOffset, properties, littleEndian) { let j = byteOffset; let flags = 0; if (properties.contentType) flags = flags | 0x8000; if (properties.contentEncoding) flags = flags | 0x4000; if (properties.headers) flags = flags | 0x2000; if (properties.deliveryMode) flags = flags | 0x1000; if (properties.priority) flags = flags | 0x0800; if (properties.correlationId) flags = flags | 0x0400; if (properties.replyTo) flags = flags | 0x0200; if (properties.expiration) flags = flags | 0x0100; if (properties.messageId) flags = flags | 0x0080; if (properties.timestamp) flags = flags | 0x0040; if (properties.type) flags = flags | 0x0020; if (properties.userId) flags = flags | 0x0010; if (properties.appId) flags = flags | 0x0008; this.setUint16(j, flags, littleEndian); j += 2; if (properties.contentType) { j += this.setShortString(j, properties.contentType); } if (properties.contentEncoding) { j += this.setShortString(j, properties.contentEncoding); } if (properties.headers) { j += this.setTable(j, properties.headers); } if (properties.deliveryMode) { this.setUint8(j, properties.deliveryMode); j += 1; } if (properties.priority) { this.setUint8(j, properties.priority); j += 1; } if (properties.correlationId) { j += this.setShortString(j, properties.correlationId); } if (properties.replyTo) { j += this.setShortString(j, properties.replyTo); } if (properties.expiration) { j += this.setShortString(j, properties.expiration); } if (properties.messageId) { j += this.setShortString(j, properties.messageId); } if (properties.timestamp) { const unixEpoch = Math.floor(Number(properties.timestamp) / 1000); this.setInt64(j, unixEpoch, littleEndian); j += 8; } if (properties.type) { j += this.setShortString(j, properties.type); } if (properties.userId) { j += this.setShortString(j, properties.userId); } if (properties.appId) { j += this.setShortString(j, properties.appId); } const len = j - byteOffset; return len; } getTable(byteOffset, littleEndian) { const table = {}; let i = byteOffset; const len = this.getUint32(byteOffset, littleEndian); i += 4; for (; i < byteOffset + 4 + len;) { const [k, strLen] = this.getShortString(i); i += strLen; const [v, vLen] = this.getField(i, littleEndian); i += vLen; table[k] = v; } return [table, len + 4]; } setTable(byteOffset, table, littleEndian) { let i = byteOffset + 4; for (const [key, value] of Object.entries(table)) { if (value === undefined) continue; i += this.setShortString(i, key); i += this.setField(i, value, littleEndian); } this.setUint32(byteOffset, i - byteOffset - 4, littleEndian); return i - byteOffset; } getField(byteOffset, littleEndian) { let i = byteOffset; const k = this.getUint8(i); i += 1; const type = String.fromCharCode(k); let v; let len; switch (type) { case "t": v = this.getUint8(i) === 1; i += 1; break; case "b": v = this.getInt8(i); i += 1; break; case "B": v = this.getUint8(i); i += 1; break; case "s": v = this.getInt16(i, littleEndian); i += 2; break; case "u": v = this.getUint16(i, littleEndian); i += 2; break; case "I": v = this.getInt32(i, littleEndian); i += 4; break; case "i": v = this.getUint32(i, littleEndian); i += 4; break; case "l": v = this.getInt64(i, littleEndian); i += 8; break; case "f": v = this.getFloat32(i, littleEndian); i += 4; break; case "d": v = this.getFloat64(i, littleEndian); i += 8; break; case "S": [v, len] = this.getLongString(i, littleEndian); i += len; break; case "F": [v, len] = this.getTable(i, littleEndian); i += len; break; case "A": [v, len] = this.getArray(i, littleEndian); i += len; break; case "x": [v, len] = this.getByteArray(i, littleEndian); i += len; break; case "T": v = new Date(this.getInt64(i, littleEndian) * 1000); i += 8; break; case "V": v = null; break; case "D": { const scale = this.getUint8(i); i += 1; const value = this.getUint32(i, littleEndian); i += 4; v = value / 10 ** scale; break; } default: throw new Error(`Field type '${k}' not supported`); } return [v, i - byteOffset]; } setField(byteOffset, field, littleEndian) { let i = byteOffset; switch (typeof field) { case "string": this.setUint8(i, "S".charCodeAt(0)); i += 1; i += this.setLongString(i, field, littleEndian); break; case "boolean": this.setUint8(i, "t".charCodeAt(0)); i += 1; this.setUint8(i, field ? 1 : 0); i += 1; break; case "bigint": this.setUint8(i, "l".charCodeAt(0)); i += 1; this.setBigInt64(i, field, littleEndian); i += 8; break; case "number": if (Number.isInteger(field)) { if (-(2 ** 32) < field && field < 2 ** 32) { this.setUint8(i, "I".charCodeAt(0)); i += 1; this.setInt32(i, field, littleEndian); i += 4; } else { this.setUint8(i, "l".charCodeAt(0)); i += 1; this.setInt64(i, field, littleEndian); i += 8; } } else { if (-(2 ** 32) < field && field < 2 ** 32) { this.setUint8(i, "f".charCodeAt(0)); i += 1; this.setFloat32(i, field, littleEndian); i += 4; } else { this.setUint8(i, "d".charCodeAt(0)); i += 1; this.setFloat64(i, field, littleEndian); i += 8; } } break; case "undefined": this.setUint8(i, "V".charCodeAt(0)); i += 1; break; case "object": if (Array.isArray(field)) { this.setUint8(i, "A".charCodeAt(0)); i += 1; i += this.setArray(i, field, littleEndian); } else if (field instanceof Uint8Array) { this.setUint8(i, "x".charCodeAt(0)); i += 1; i += this.setByteArray(i, field); } else if (field instanceof ArrayBuffer) { this.setUint8(i, "x".charCodeAt(0)); i += 1; i += this.setByteArray(i, new Uint8Array(field)); } else if (field instanceof Date) { this.setUint8(i, "T".charCodeAt(0)); i += 1; const unixEpoch = Math.floor(Number(field) / 1000); this.setInt64(i, unixEpoch, littleEndian); i += 8; } else if (field === null) { this.setUint8(i, "V".charCodeAt(0)); i += 1; } else { this.setUint8(i, "F".charCodeAt(0)); i += 1; i += this.setTable(i, field, littleEndian); } break; default: throw new Error(`Unsupported field type '${field}'`); } return i - byteOffset; } getArray(byteOffset, littleEndian) { const len = this.getUint32(byteOffset, littleEndian); byteOffset += 4; const endOffset = byteOffset + len; const v = []; for (; byteOffset < endOffset;) { const [field, fieldLen] = this.getField(byteOffset, littleEndian); byteOffset += fieldLen; v.push(field); } return [v, len + 4]; } setArray(byteOffset, array, littleEndian) { const start = byteOffset; byteOffset += 4; array.forEach((e) => { byteOffset += this.setField(byteOffset, e, littleEndian); }); this.setUint32(start, byteOffset - start - 4, littleEndian); return byteOffset - start; } getByteArray(byteOffset, littleEndian) { const len = this.getUint32(byteOffset, littleEndian); byteOffset += 4; const v = new Uint8Array(this.buffer, this.byteOffset + byteOffset, len); return [v, len + 4]; } setByteArray(byteOffset, data, littleEndian) { this.setUint32(byteOffset, data.byteLength, littleEndian); byteOffset += 4; const view = new Uint8Array(this.buffer, this.byteOffset + byteOffset, data.byteLength); view.set(data); return data.byteLength + 4; } } var Type; (function (Type) { Type[Type["METHOD"] = 1] = "METHOD"; Type[Type["HEADER"] = 2] = "HEADER"; Type[Type["BODY"] = 3] = "BODY"; Type[Type["HEARTBEAT"] = 8] = "HEARTBEAT"; })(Type || (Type = {})); var End; (function (End) { End[End["CODE"] = 206] = "CODE"; })(End || (End = {})); var ClassId; (function (ClassId) { ClassId[ClassId["CONNECTION"] = 10] = "CONNECTION"; ClassId[ClassId["CHANNEL"] = 20] = "CHANNEL"; ClassId[ClassId["EXCHANGE"] = 40] = "EXCHANGE"; ClassId[ClassId["QUEUE"] = 50] = "QUEUE"; ClassId[ClassId["BASIC"] = 60] = "BASIC"; ClassId[ClassId["TX"] = 90] = "TX"; ClassId[ClassId["CONFIRM"] = 85] = "CONFIRM"; })(ClassId || (ClassId = {})); var ConnectionMethod; (function (ConnectionMethod) { ConnectionMethod[ConnectionMethod["START"] = 10] = "START"; ConnectionMethod[ConnectionMethod["START_OK"] = 11] = "START_OK"; ConnectionMethod[ConnectionMethod["SECURE"] = 20] = "SECURE"; ConnectionMethod[ConnectionMethod["SECURE_OK"] = 21] = "SECURE_OK"; ConnectionMethod[ConnectionMethod["TUNE"] = 30] = "TUNE"; ConnectionMethod[ConnectionMethod["TUNE_OK"] = 31] = "TUNE_OK"; ConnectionMethod[ConnectionMethod["OPEN"] = 40] = "OPEN"; ConnectionMethod[ConnectionMethod["OPEN_OK"] = 41] = "OPEN_OK"; ConnectionMethod[ConnectionMethod["CLOSE"] = 50] = "CLOSE"; ConnectionMethod[ConnectionMethod["CLOSE_OK"] = 51] = "CLOSE_OK"; ConnectionMethod[ConnectionMethod["BLOCKED"] = 60] = "BLOCKED"; ConnectionMethod[ConnectionMethod["UNBLOCKED"] = 61] = "UNBLOCKED"; ConnectionMethod[ConnectionMethod["UPDATE_SECRET"] = 70] = "UPDATE_SECRET"; ConnectionMethod[ConnectionMethod["UPDATE_SECRET_OK"] = 71] = "UPDATE_SECRET_OK"; })(ConnectionMethod || (ConnectionMethod = {})); var ChannelMethod; (function (ChannelMethod) { ChannelMethod[ChannelMethod["OPEN"] = 10] = "OPEN"; ChannelMethod[ChannelMethod["OPEN_OK"] = 11] = "OPEN_OK"; ChannelMethod[ChannelMethod["FLOW"] = 20] = "FLOW"; ChannelMethod[ChannelMethod["FLOW_OK"] = 21] = "FLOW_OK"; ChannelMethod[ChannelMethod["CLOSE"] = 40] = "CLOSE"; ChannelMethod[ChannelMethod["CLOSE_OK"] = 41] = "CLOSE_OK"; })(ChannelMethod || (ChannelMethod = {})); var ExchangeMethod; (function (ExchangeMethod) { ExchangeMethod[ExchangeMethod["DECLARE"] = 10] = "DECLARE"; ExchangeMethod[ExchangeMethod["DECLARE_OK"] = 11] = "DECLARE_OK"; ExchangeMethod[ExchangeMethod["DELETE"] = 20] = "DELETE"; ExchangeMethod[ExchangeMethod["DELETE_OK"] = 21] = "DELETE_OK"; ExchangeMethod[ExchangeMethod["BIND"] = 30] = "BIND"; ExchangeMethod[ExchangeMethod["BIND_OK"] = 31] = "BIND_OK"; ExchangeMethod[ExchangeMethod["UNBIND"] = 40] = "UNBIND"; ExchangeMethod[ExchangeMethod["UNBIND_OK"] = 51] = "UNBIND_OK"; })(ExchangeMethod || (ExchangeMethod = {})); var QueueMethod; (function (QueueMethod) { QueueMethod[QueueMethod["DECLARE"] = 10] = "DECLARE"; QueueMethod[QueueMethod["DECLARE_OK"] = 11] = "DECLARE_OK"; QueueMethod[QueueMethod["BIND"] = 20] = "BIND"; QueueMethod[QueueMethod["BIND_OK"] = 21] = "BIND_OK"; QueueMethod[QueueMethod["PURGE"] = 30] = "PURGE"; QueueMethod[QueueMethod["PURGE_OK"] = 31] = "PURGE_OK"; QueueMethod[QueueMethod["DELETE"] = 40] = "DELETE"; QueueMethod[QueueMethod["DELETE_OK"] = 41] = "DELETE_OK"; QueueMethod[QueueMethod["UNBIND"] = 50] = "UNBIND"; QueueMethod[QueueMethod["UNBIND_OK"] = 51] = "UNBIND_OK"; })(QueueMethod || (QueueMethod = {})); var BasicMethod; (function (BasicMethod) { BasicMethod[BasicMethod["QOS"] = 10] = "QOS"; BasicMethod[BasicMethod["QOS_OK"] = 11] = "QOS_OK"; BasicMethod[BasicMethod["CONSUME"] = 20] = "CONSUME"; BasicMethod[BasicMethod["CONSUME_OK"] = 21] = "CONSUME_OK"; BasicMethod[BasicMethod["CANCEL"] = 30] = "CANCEL"; BasicMethod[BasicMethod["CANCEL_OK"] = 31] = "CANCEL_OK"; BasicMethod[BasicMethod["PUBLISH"] = 40] = "PUBLISH"; BasicMethod[BasicMethod["RETURN"] = 50] = "RETURN"; BasicMethod[BasicMethod["DELIVER"] = 60] = "DELIVER"; BasicMethod[BasicMethod["GET"] = 70] = "GET"; BasicMethod[BasicMethod["GET_OK"] = 71] = "GET_OK"; BasicMethod[BasicMethod["GET_EMPTY"] = 72] = "GET_EMPTY"; BasicMethod[BasicMethod["ACK"] = 80] = "ACK"; BasicMethod[BasicMethod["REJECT"] = 90] = "REJECT"; BasicMethod[BasicMethod["RECOVER_ASYNC"] = 100] = "RECOVER_ASYNC"; BasicMethod[BasicMethod["RECOVER"] = 110] = "RECOVER"; BasicMethod[BasicMethod["RECOVER_OK"] = 111] = "RECOVER_OK"; BasicMethod[BasicMethod["NACK"] = 120] = "NACK"; })(BasicMethod || (BasicMethod = {})); var TxMethod; (function (TxMethod) { TxMethod[TxMethod["SELECT"] = 10] = "SELECT"; TxMethod[TxMethod["SELECT_OK"] = 11] = "SELECT_OK"; TxMethod[TxMethod["COMMIT"] = 20] = "COMMIT"; TxMethod[TxMethod["COMMIT_OK"] = 21] = "COMMIT_OK"; TxMethod[TxMethod["ROLLBACK"] = 30] = "ROLLBACK"; TxMethod[TxMethod["ROLLBACK_OK"] = 31] = "ROLLBACK_OK"; })(TxMethod || (TxMethod = {})); var ConfirmMethod; (function (ConfirmMethod) { ConfirmMethod[ConfirmMethod["SELECT"] = 10] = "SELECT"; ConfirmMethod[ConfirmMethod["SELECT_OK"] = 11] = "SELECT_OK"; })(ConfirmMethod || (ConfirmMethod = {})); class Writer { constructor(options) { this.view = new AMQPView(new ArrayBuffer(options.bufferSize)); this.offset = 0; this.writeUint8(options.type); this.writeUint16(options.channel); this.writeUint32(options.frameSize ?? 0); this.writeUint16(options.classId); this.writeUint16(options.method); } finalize() { const currentFrameSize = this.view.getUint32(3); if (currentFrameSize === 0) { const value = this.offset - 7; this.view.setUint32(3, value); } this.view.setUint8(this.offset, End.CODE); this.offset += 1; } getBuffer() { return this.view.buffer; } toUint8Array() { return new Uint8Array(this.view.buffer, 0, this.offset); } writeUint8(value) { this.view.setUint8(this.offset, value); this.offset += 1; } writeUint16(value) { this.view.setUint16(this.offset, value); this.offset += 2; } writeUint32(value) { this.view.setUint32(this.offset, value); this.offset += 4; } writeUint64(value) { this.view.setUint64(this.offset, value); this.offset += 8; } writeShortString(value) { const bytesWritten = this.view.setShortString(this.offset, value); this.offset += bytesWritten; } writeLongString(value) { const bytesWritten = this.view.setLongString(this.offset, value); this.offset += bytesWritten; } writeTable(table) { const bytesWritten = this.view.setTable(this.offset, table); this.offset += bytesWritten; } } class AMQPConsumer { constructor(channel, tag, onMessage) { this.closed = false; this.channel = channel; this.tag = tag; this.onMessage = onMessage; } wait(timeout) { if (this.closedError) return Promise.reject(this.closedError); if (this.closed) return Promise.resolve(); return new Promise((resolve, reject) => { this.resolveWait = resolve; this.rejectWait = reject; if (timeout) { const onTimeout = () => reject(new AMQPError("Timeout", this.channel.connection)); this.timeoutId = setTimeout(onTimeout, timeout); } }); } cancel() { if (this.channel.closed) return Promise.resolve(this.channel); if (!this.cancelPromise) { this.cancelPromise = this.channel.basicCancel(this.tag); } return this.cancelPromise; } setClosed(err) { this.closed = true; if (err) this.closedError = err; if (this.timeoutId) clearTimeout(this.timeoutId); if (err) { if (this.rejectWait) this.rejectWait(err); } else { if (this.resolveWait) this.resolveWait(); } } } class AMQPGeneratorConsumer extends AMQPConsumer { constructor(channel, tag) { super(channel, tag, (msg) => { if (this.messageResolver) { this.messageResolver(msg); this.messageResolver = null; } else if (this.messageQueue) { this.messageQueue.push(msg); } }); this.messageQueue = []; this.messageResolver = null; } get messages() { if (this.generator) { return this.generator; } this.generator = this.generateMessages(); return this.generator; } async *generateMessages() { try { while (!this.closedError && !this.closed) { const msg = this.messageQueue.shift(); if (msg) { yield msg; } else { const msg = await new Promise((resolve) => { this.messageResolver = resolve; }); if (this.closedError || this.closed) break; yield msg; } } if (this.closedError) throw this.closedError; } finally { try { await this.cancel(); } catch { } } } setClosed(err) { super.setClosed(err); if (this.messageResolver) { const resolver = this.messageResolver; this.messageResolver = null; resolver(undefined); } } } class AMQPChannel { constructor(connection, id) { this.consumers = new Map(); this.rpcQueue = Promise.resolve(true); this.rpcCallbacks = []; this.unconfirmedPublishes = []; this.closed = false; this.confirmId = 0; this.connection = connection; this.id = id; this.onerror = (reason) => { this.logger?.error(`channel ${this.id} closed: ${reason}`); this.connection.onerror(new AMQPError(`Channel ${this.id} closed: ${reason}`, this.connection)); }; } get logger() { return this.connection.logger; } open() { const channelOpen = new Writer({ bufferSize: 13, type: Type.METHOD, channel: this.id, frameSize: 5, classId: ClassId.CHANNEL, method: ChannelMethod.OPEN, }); channelOpen.writeUint8(0); channelOpen.finalize(); return this.sendRpc(channelOpen); } prefetch(prefetchCount) { return this.basicQos(prefetchCount); } onReturn(message) { this.logger?.error("Message returned from server", message); } close(reason = "", code = 200) { if (this.closed) return this.rejectClosed(); this.closed = true; const frame = new Writer({ bufferSize: 512, type: Type.METHOD, channel: this.id, classId: ClassId.CHANNEL, method: ChannelMethod.CLOSE, }); frame.writeUint16(code); frame.writeShortString(reason); frame.writeUint16(0); frame.writeUint16(0); frame.finalize(); return this.sendRpc(frame); } basicGet(queue, { noAck = true } = {}) { if (this.closed) return this.rejectClosed(); const frame = new Writer({ bufferSize: 512, type: Type.METHOD, channel: this.id, classId: ClassId.BASIC, method: BasicMethod.GET, }); frame.writeUint16(0); frame.writeShortString(queue); frame.writeUint8(noAck ? 1 : 0); frame.finalize(); return this.sendRpc(frame); } basicConsume(queue, { tag = "", noAck = true, exclusive = false, args = {} } = {}, callback) { if (this.closed) return this.rejectClosed(); const frame = new Writer({ bufferSize: 4096, type: Type.METHOD, channel: this.id, classId: ClassId.BASIC, method: BasicMethod.CONSUME, }); frame.writeUint16(0); frame.writeShortString(queue); frame.writeShortString(tag); let bits = 0; if (noAck) bits = bits | (1 << 1); if (exclusive) bits = bits | (1 << 2); frame.writeUint8(bits); frame.writeTable(args); frame.finalize(); return new Promise((resolve, reject) => { this.sendRpc(frame) .then((consumerTag) => { let consumer; if (callback) { consumer = new AMQPConsumer(this, consumerTag, callback); } else { consumer = new AMQPGeneratorConsumer(this, consumerTag); } this.consumers.set(consumerTag, consumer); resolve(consumer); }) .catch(reject); }); } basicCancel(tag) { if (this.closed) return this.rejectClosed(); const frame = new Writer({ bufferSize: 512, type: Type.METHOD, channel: this.id, classId: ClassId.BASIC, method: BasicMethod.CANCEL, }); frame.writeShortString(tag); frame.writeUint8(0); frame.finalize(); return new Promise((resolve, reject) => { this.sendRpc(frame) .then((consumerTag) => { const consumer = this.consumers.get(consumerTag); if (consumer) { consumer.setClosed(); this.consumers.delete(consumerTag); } resolve(this); }) .catch(reject); }); } basicAck(deliveryTag, multiple = false) { if (this.closed) return this.rejectClosed(); const frame = new Writer({ bufferSize: 21, type: Type.METHOD, channel: this.id, frameSize: 13, classId: ClassId.BASIC, method: BasicMethod.ACK, }); frame.writeUint64(deliveryTag); frame.writeUint8(multiple ? 1 : 0); frame.finalize(); return this.connection.send(frame.toUint8Array()); } basicNack(deliveryTag, requeue = false, multiple = false) { if (this.closed) return this.rejectClosed(); const frame = new Writer({ bufferSize: 21, type: Type.METHOD, channel: this.id, frameSize: 13, classId: ClassId.BASIC, method: BasicMethod.NACK, }); frame.writeUint64(deliveryTag); let bits = 0; if (multiple) bits = bits | (1 << 0); if (requeue) bits = bits | (1 << 1); frame.writeUint8(bits); frame.finalize(); return this.connection.send(frame.toUint8Array()); } basicReject(deliveryTag, requeue = false) { if (this.closed) return this.rejectClosed(); const frame = new Writer({ bufferSize: 21, type: Type.METHOD, channel: this.id, frameSize: 13, classId: ClassId.BASIC, method: BasicMethod.REJECT, }); frame.writeUint64(deliveryTag); frame.writeUint8(requeue ? 1 : 0); frame.finalize(); return this.connection.send(frame.toUint8Array()); } basicRecover(requeue = false) { if (this.closed) return this.rejectClosed(); const frame = new Writer({ bufferSize: 13, type: Type.METHOD, channel: this.id, frameSize: 5, classId: ClassId.BASIC, method: BasicMethod.RECOVER, }); frame.writeUint8(requeue ? 1 : 0); frame.finalize(); return this.sendRpc(frame); } async basicPublish(exchange, routingKey, data, properties = {}, mandatory = false, immediate = false) { if (this.closed) return this.rejectClosed(); if (this.connection.blocked) return Promise.reject(new AMQPError(`Connection blocked by server: ${this.connection.blocked}`, this.connection)); let body; if (typeof Buffer !== "undefined" && data instanceof Buffer) { body = data; } else if (data instanceof Uint8Array) { body = data; } else if (data instanceof ArrayBuffer) { body = new Uint8Array(data); } else if (data === null) { body = new Uint8Array(0); } else if (typeof data === "string") { body = this.connection.textEncoder.encode(data); } else { throw new TypeError(`Invalid type ${typeof data} for parameter data`); } let j = 0; let buffer = this.connection.bufferPool.pop() || new AMQPView(new ArrayBuffer(this.connection.frameMax)); buffer.setUint8(j, Type.METHOD); j += 1; buffer.setUint16(j, this.id); j += 2; j += 4; buffer.setUint16(j, ClassId.BASIC); j += 2; buffer.setUint16(j, BasicMethod.PUBLISH); j += 2; buffer.setUint16(j, 0); j += 2; j += buffer.setShortString(j, exchange); j += buffer.setShortString(j, routingKey); let bits = 0; if (mandatory) bits = bits | (1 << 0); if (immediate) bits = bits | (1 << 1); buffer.setUint8(j, bits); j += 1; buffer.setUint8(j, End.CODE); j += 1; buffer.setUint32(3, j - 8); const headerStart = j; buffer.setUint8(j, Type.HEADER); j += 1; buffer.setUint16(j, this.id); j += 2; j += 4; buffer.setUint16(j, ClassId.BASIC); j += 2; buffer.setUint16(j, 0); j += 2; buffer.setUint32(j, 0); j += 4; buffer.setUint32(j, body.byteLength); j += 4; j += buffer.setProperties(j, properties); buffer.setUint8(j, End.CODE); j += 1; buffer.setUint32(headerStart + 3, j - headerStart - 8); let bufferView = new Uint8Array(buffer.buffer); const bodyFrameCount = Math.ceil(body.byteLength / (this.connection.frameMax - 8)); const bufferSize = j + body.byteLength + 8 * bodyFrameCount; if (buffer.byteLength < bufferSize) { const newBuffer = new ArrayBuffer(bufferSize); const newBufferView = new Uint8Array(newBuffer); newBufferView.set(bufferView.subarray(0, j)); buffer = new AMQPView(newBuffer); bufferView = newBufferView; } for (let bodyPos = 0; bodyPos < body.byteLength;) { const frameSize = Math.min(body.byteLength - bodyPos, this.connection.frameMax - 8); const dataSlice = body.subarray(bodyPos, bodyPos + frameSize); buffer.setUint8(j, Type.BODY); j += 1; buffer.setUint16(j, this.id); j += 2; buffer.setUint32(j, frameSize); j += 4; bufferView.set(dataSlice, j); j += frameSize; buffer.setUint8(j, End.CODE); j += 1; bodyPos += frameSize; } const sendFrames = this.connection.send(bufferView.subarray(0, j)); if (this.confirmId) { const wait4Confirm = new Promise((resolve, reject) => this.unconfirmedPublishes.push([this.confirmId++, resolve, reject])); return sendFrames.then(() => wait4Confirm).finally(() => this.connection.bufferPool.push(buffer)); } else { return sendFrames.then(() => 0).finally(() => this.connection.bufferPool.push(buffer)); } } basicQos(prefetchCount, prefetchSize = 0, global = false) { if (this.closed) return this.rejectClosed(); const frame = new Writer({ bufferSize: 19, type: Type.METHOD, channel: this.id, frameSize: 11, classId: ClassId.BASIC, method: BasicMethod.QOS, }); frame.writeUint32(prefetchSize); frame.writeUint16(prefetchCount); frame.writeUint8(global ? 1 : 0); frame.finalize(); return this.sendRpc(frame); } basicFlow(active = true) { if (this.closed) return this.rejectClosed(); const frame = new Writer({ bufferSize: 13, type: Type.METHOD, channel: this.id, frameSize: 5, classId: ClassId.CHANNEL, method: BasicMethod.CONSUME, }); frame.writeUint8(active ? 1 : 0); frame.finalize(); return this.sendRpc(frame); } confirmSelect() { if (this.closed) return this.rejectClosed(); const frame = new Writer({ bufferSize: 13, type: Type.METHOD, channel: this.id, frameSize: 5, classId: ClassId.CONFIRM, method: ConfirmMethod.SELECT, }); frame.writeUint8(0); frame.finalize(); return this.sendRpc(frame); } queueDeclare(name = "", { passive = false, durable = name !== "", autoDelete = name === "", exclusive = name === "" } = {}, args = {}) { if (this.closed) return this.rejectClosed(); const declare = new Writer({ bufferSize: 4096, type: Type.METHOD, channel: this.id, classId: ClassId.QUEUE, method: QueueMethod.DECLARE, }); declare.writeUint16(0); declare.writeShortString(name); let bits = 0; if (passive) bits = bits | (1 << 0); if (durable) bits = bits | (1 << 1); if (exclusive) bits = bits | (1 << 2); if (autoDelete) bits = bits | (1 << 3); declare.writeUint8(bits); declare.writeTable(args); declare.finalize(); return this.sendRpc(declare); } queueDelete(name = "", { ifUnused = false, ifEmpty = false } = {}) { if (this.closed) return this.rejectClosed(); const frame = new Writer({ bufferSize: 512, type: Type.METHOD, channel: this.id, classId: ClassId.QUEUE, method: QueueMethod.DELETE, }); frame.writeUint16(0); frame.writeShortString(name); let bits = 0; if (ifUnused) bits = bits | (1 << 0); if (ifEmpty) bits = bits | (1 << 1); frame.writeUint8(bits); frame.finalize(); return this.sendRpc(frame); } queueBind(queue, exchange, routingKey, args = {}) { if (this.closed) return this.rejectClosed(); const bind = new Writer({ bufferSize: 4096, type: Type.METHOD, channel: this.id, classId: ClassId.QUEUE, method: BasicMethod.CONSUME, }); bind.writeUint16(0); bind.writeShortString(queue); bind.writeShortString(exchange); bind.writeShortString(routingKey); bind.writeUint8(0); bind.writeTable(args); bind.finalize(); return this.sendRpc(bind); } queueUnbind(queue, exchange, routingKey, args = {}) { if (this.closed) return this.rejectClosed(); const unbind = new Writer({ bufferSize: 4096, type: Type.METHOD, channel: this.id, classId: ClassId.QUEUE, method: QueueMethod.UNBIND, }); unbind.writeUint16(0); unbind.writeShortString(queue); unbind.writeShortString(exchange); unbind.writeShortString(routingKey); unbind.writeTable(args); unbind.finalize(); return this.sendRpc(unbind); } queuePurge(queue) { if (this.closed) return this.rejectClosed(); const purge = new Writer({ bufferSize: 512, type: Type.METHOD, channel: this.id, classId: ClassId.QUEUE, method: BasicMethod.CANCEL, }); purge.writeUint16(0); purge.writeShortString(queue); purge.writeUint8(0); purge.finalize(); return this.sendRpc(purge); } exchangeDeclare(name, type, { passive = false, durable = true, autoDelete = false, internal = false } = {}, args = {}) { if (this.closed) return this.rejectClosed(); const frame = new Writer({ bufferSize: 4096, type: Type.METHOD, channel: this.id, classId: ClassId.EXCHANGE, method: ExchangeMethod.DECLARE, }); frame.writeUint16(0); frame.writeShortString(name); frame.writeShortString(type); let bits = 0; if (passive) bits = bits | (1 << 0); if (durable) bits = bits | (1 << 1); if (autoDelete) bits = bits | (1 << 2); if (internal) bits = bits | (1 << 3); frame.writeUint8(bits); frame.writeTable(args); frame.finalize(); return this.sendRpc(frame); } exchangeDelete(name, { ifUnused = false } = {}) { if (this.closed) return this.rejectClosed(); const frame = new Writer({ bufferSize: 512, type: Type.METHOD, channel: this.id, classId: ClassId.EXCHANGE, method: BasicMethod.CONSUME, }); frame.writeUint16(0); frame.writeShortString(name); let bits = 0; if (ifUnused) bits = bits | (1 << 0); frame.writeUint8(bits); frame.finalize(); return this.sendRpc(frame); } exchangeBind(destination, source, routingKey = "", args = {}) { if (this.closed) return this.rejectClosed(); const bind = new Writer({ bufferSize: 4096, type: Type.METHOD, channel: this.id, classId: ClassId.EXCHANGE, method: BasicMethod.CANCEL, }); bind.writeUint16(0); bind.writeShortString(destination); bind.writeShortString(source); bind.writeShortString(routingKey); bind.writeUint8(0); bind.writeTable(args); bind.finalize(); return this.sendRpc(bind); } exchangeUnbind(destination, source, routingKey = "", args = {}) { if (this.closed) return this.rejectClosed(); const unbind = new Writer({ bufferSize: 4096, type: Type.METHOD, channel: this.id, classId: ClassId.EXCHANGE, method: ExchangeMethod.UNBIND, }); unbind.writeUint16(0); unbind.writeShortString(destination); unbind.writeShortString(source); unbind.writeShortString(routingKey); unbind.writeUint8(0); unbind.writeTable(args); unbind.finalize(); return this.sendRpc(unbind); } txSelect() { return this.txMethod(10); } txCommit() { return this.txMethod(20); } txRollback() { return this.txMethod(30); } txMethod(methodId) { if (this.closed) return this.rejectClosed(); const frame = new Writer({ bufferSize: 12, type: Type.METHOD, channel: this.id, frameSize: 4, classId: ClassId.TX, method: methodId, }); frame.finalize(); return this.sendRpc(frame); } sendRpc(frame) { const bytes = frame.toUint8Array(); return new Promise((resolve, reject) => { this.rpcQueue = this.rpcQueue .then(() => { this.rpcCallbacks.push([resolve, reject]); return this.connection.send(bytes).catch((err) => { const callbacks = this.rpcCallbacks.pop(); if (callbacks) { callbacks[1](err); } else { reject(err); } }); }) .catch(reject); }); } setClosed(err) { const closedByServer = err !== undefined; err || (err = new Error("Connection closed by client")); if (!this.closed) { this.closed = true; this.consumers.forEach((consumer) => consumer.setClosed(closedByServer ? err : undefined)); this.consumers.clear(); this.rpcCallbacks.forEach(([, reject]) => reject(err)); this.rpcCallbacks.length = 0; this.unconfirmedPublishes.forEach(([, , reject]) => reject(err)); this.unconfirmedPublishes.length = 0; if (closedByServer) this.onerror(err.message); } } rejectClosed() { return Promise.reject(new AMQPError("Channel is closed", this.connection)); } publishConfirmed(deliveryTag, multiple, nack) { const idx = this.unconfirmedPublishes.findIndex(([tag]) => tag === deliveryTag); if (idx !== -1) { const confirmed = multiple ? this.unconfirmedPublishes.splice(0, idx + 1) : this.unconfirmedPublishes.splice(idx, 1); confirmed.forEach(([tag, resolve, reject]) => { if (nack) reject(new Error("Message rejected")); else resolve(tag); }); } else { this.logger?.warn("Cant find unconfirmed deliveryTag", deliveryTag, "multiple:", multiple, "nack:", nack); } } onMessageReady(message) { message.body = message._rawBytes; if (this.delivery) { delete this.delivery; this.deliver(message); } else if (this.getMessage) { delete this.getMessage; this.resolveRPC(message); } else { delete this.returned; this.onReturn(message); } } resolveRPC(value) { const callbacks = this.rpcCallbacks.shift(); if (callbacks) { callbacks[0](value); } return value; } rejectRPC(err) { const callbacks = this.rpcCallbacks.shift(); if (callbacks) { callbacks[1](err); } return err; } deliver(message) { queueMicrotask(() => { const consumer = this.consumers.get(message.consumerTag); if (consumer) { consumer.onMessage(message); } else { this.logger?.warn("Consumer", message.consumerTag, "not available on channel", this.id); } }); } } class AMQPMessage { get isAcked() { return this.acked; } constructor(channel) { this.exchange = ""; this.routingKey = ""; this.properties = {}; this.bodySize = 0; this._rawBytes = null; this.bodyPos = 0; this.body = null; this.deliveryTag = 0; this.consumerTag = ""; this.redelivered = false; this.acked = false; this.channel = channel; } bodyToString() { if (this._rawBytes) { if (typeof Buffer !== "undefined") return Buffer.from(this._rawBytes).toString(); else return new TextDecoder().decode(this._rawBytes); } else { return null; } } bodyString() { return this.bodyToString(); } ack(multiple = false) { if (this.acked) return Promise.resolve(); this.acked = true; return this.channel.basicAck(this.deliveryTag, multiple); } nack(requeue = false, multiple = false) { if (this.acked) return Promise.resolve(); this.acked = true; return this.channel.basicNack(this.deliveryTag, requeue, multiple); } reject(requeue = false) { if (this.acked) return Promise.resolve(); this.acked = true; return this.channel.basicReject(this.deliveryTag, requeue); } cancelConsumer() { return this.channel.basicCancel(this.consumerTag); } } const VERSION = "4.0.0"; const MI