@azure/service-bus
Version:
Azure Service Bus SDK for JavaScript
143 lines • 6.59 kB
JavaScript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { Constants, ErrorNameConditionMapper, } from "@azure/core-amqp";
import { receiverLogger as logger } from "../log";
import { LinkEntity } from "./linkEntity";
import { DispositionType } from "../serviceBusMessage";
import { getUniqueName } from "../util/utils";
import { onMessageSettled, createReceiverOptions, } from "./shared";
import { translateServiceBusError } from "../serviceBusError";
/**
* @internal
* Describes the MessageReceiver that will receive messages from ServiceBus.
*/
export class MessageReceiver extends LinkEntity {
constructor(identifier, context, entityPath, receiverType, options) {
super(entityPath, entityPath, context, receiverType, logger, {
address: entityPath,
audience: `${context.config.endpoint}${entityPath}`,
});
this.identifier = identifier;
/**
* Maintains a map of deliveries that
* are being actively disposed. It acts as a store for correlating the responses received for
* active dispositions.
*/
this._deliveryDispositionMap = new Map();
this.receiverType = receiverType;
this.receiveMode = options.receiveMode || "peekLock";
// If explicitly set to false then autoComplete is false else true (default).
this.autoComplete =
options.autoCompleteMessages === false ? options.autoCompleteMessages : true;
this._lockRenewer = options.lockRenewer;
}
/**
* Creates the options that need to be specified while creating an AMQP receiver link.
*/
_createReceiverOptions(useNewName, handlers) {
const rcvrOptions = createReceiverOptions(useNewName ? getUniqueName(this.baseName) : this.name, this.receiveMode, {
address: this.address,
}, this.identifier, {
onSettled: (context) => {
return onMessageSettled(this.logPrefix, context.delivery, this._deliveryDispositionMap);
},
...handlers,
});
return rcvrOptions;
}
/**
* Creates a new AMQP receiver under a new AMQP session.
*/
async _init(options, abortSignal) {
try {
await this.initLink(options, abortSignal);
// It is possible for someone to close the receiver and then start it again.
// Thus make sure that the receiver is present in the client cache.
this._context.messageReceivers[this.name] = this;
}
catch (err) {
const translatedError = translateServiceBusError(err);
logger.logError(translatedError, "%s An error occured while creating the receiver", this.logPrefix);
// Fix the unhelpful error messages for the OperationTimeoutError that comes from `rhea-promise`.
if (translatedError.code === "OperationTimeoutError") {
translatedError.message =
"Failed to create a receiver within allocated time and retry attempts.";
}
throw translatedError;
}
}
createRheaLink(options, _abortSignal) {
return this._context.connection.createReceiver(options);
}
/**
* Clears lock renewal timers on all active messages, clears token remewal for current receiver,
* removes current MessageReceiver instance from cache, and closes the underlying AMQP receiver.
* @returns Promise<void>.
*/
async close() {
this._lockRenewer?.stopAll(this);
await super.close();
}
/**
* Settles the message with the specified disposition.
* @param message - The ServiceBus Message that needs to be settled.
* @param operation - The disposition type.
* @param options - Optional parameters that can be provided while disposing the message.
*/
async settleMessage(message, operation, options) {
return new Promise((resolve, reject) => {
if (operation.match(/^(complete|abandon|defer|deadletter)$/) == null) {
return reject(new Error(`operation: '${operation}' is not a valid operation.`));
}
this._lockRenewer?.stop(this, message);
const delivery = message.delivery;
const timer = setTimeout(() => {
this._deliveryDispositionMap.delete(delivery.id);
logger.verbose("%s Disposition for delivery id: %d, did not complete in %d milliseconds. " +
"Hence rejecting the promise with timeout error.", this.logPrefix, delivery.id, Constants.defaultOperationTimeoutInMs);
const e = {
condition: ErrorNameConditionMapper.ServiceUnavailableError,
description: "Operation to settle the message has timed out. The disposition of the " +
"message may or may not be successful",
};
return reject(translateServiceBusError(e));
}, options.retryOptions?.timeoutInMs ?? Constants.defaultOperationTimeoutInMs);
this._deliveryDispositionMap.set(delivery.id, {
resolve: resolve,
reject: reject,
timer: timer,
});
if (operation === DispositionType.complete) {
delivery.accept();
}
else if (operation === DispositionType.abandon) {
const params = {
undeliverable_here: false,
};
if (options.propertiesToModify)
params.message_annotations = options.propertiesToModify;
delivery.modified(params);
}
else if (operation === DispositionType.defer) {
const params = {
undeliverable_here: true,
};
if (options.propertiesToModify)
params.message_annotations = options.propertiesToModify;
delivery.modified(params);
}
else if (operation === DispositionType.deadletter) {
const error = {
condition: Constants.deadLetterName,
info: {
...options.propertiesToModify,
DeadLetterReason: options.deadLetterReason,
DeadLetterErrorDescription: options.deadLetterDescription,
},
};
delivery.reject(error);
}
});
}
}
//# sourceMappingURL=messageReceiver.js.map