@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
JavaScript
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