@azure/core-amqp
Version:
Common library for amqp based azure sdks like @azure/event-hubs.
250 lines (249 loc) • 10.1 kB
JavaScript
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var requestResponseLink_exports = {};
__export(requestResponseLink_exports, {
RequestResponseLink: () => RequestResponseLink,
getCodeDescriptionAndError: () => getCodeDescriptionAndError,
onMessageReceived: () => onMessageReceived
});
module.exports = __toCommonJS(requestResponseLink_exports);
var import_abort_controller = require("@azure/abort-controller");
var import_errors = require("./errors.js");
var import_rhea_promise = require("rhea-promise");
var import_constants = require("./util/constants.js");
var import_log = require("./log.js");
var import_core_util = require("@azure/core-util");
class RequestResponseLink {
/**
* @param session - The amqp session.
* @param sender - The amqp sender link.
* @param receiver - The amqp receiver link.
*/
constructor(session, sender, receiver) {
this.session = session;
this.sender = sender;
this.receiver = receiver;
this.session = session;
this.sender = sender;
this.receiver = receiver;
this.receiver.on(import_rhea_promise.ReceiverEvents.message, (context) => {
onMessageReceived(context, this.connection.id, this._responsesMap);
});
this.sender.on(import_rhea_promise.SenderEvents.senderError, (context) => {
onSenderError(context, this.connection.id, this._responsesMap);
});
}
session;
sender;
receiver;
/**
* Maintains a map of responses that
* are being actively returned. It acts as a store for correlating the responses received for
* the send requests.
*/
_responsesMap = /* @__PURE__ */ new Map();
/**
* Provides the underlying amqp connection object.
* @returns Connection.
*/
get connection() {
return this.session.connection;
}
/**
* Indicates whether the session and the sender and receiver links are all open or closed.
* @returns boolean - `true` - `open`, `false` - `closed`.
*/
isOpen() {
return this.session.isOpen() && this.sender.isOpen() && this.receiver.isOpen();
}
/**
* Sends the given request message and returns the received response. If the operation is not
* completed in the provided timeout in milliseconds `default: 60000`, then `OperationTimeoutError` is thrown.
*
* @param request - The AMQP (request) message.
* @param options - Options that can be provided while sending a request.
* @returns Promise<Message> The AMQP (response) message.
*/
sendRequest(request, options = {}) {
const timeoutInMs = options.timeoutInMs === void 0 ? import_constants.Constants.defaultOperationTimeoutInMs : Math.max(options.timeoutInMs, 0);
const aborter = options.abortSignal;
if (!request.message_id) {
request.message_id = (0, import_rhea_promise.generate_uuid)();
}
return new Promise((resolve, reject) => {
let timer = void 0;
const rejectOnAbort = () => {
this._responsesMap.delete(request.message_id);
const address = this.receiver.address || "address";
const requestName = options.requestName;
const desc = `[${this.connection.id}] The request "${requestName}" to "${address}" has been cancelled by the user.`;
import_log.logger.info(desc);
const error = new import_abort_controller.AbortError(import_constants.StandardAbortMessage);
reject(error);
};
const onAbort = () => {
if ((0, import_core_util.isDefined)(timer)) {
clearTimeout(timer);
}
aborter.removeEventListener("abort", onAbort);
rejectOnAbort();
};
if (aborter) {
if (aborter.aborted) {
return rejectOnAbort();
}
aborter.addEventListener("abort", onAbort);
}
timer = setTimeout(() => {
this._responsesMap.delete(request.message_id);
if (aborter) {
aborter.removeEventListener("abort", onAbort);
}
const address = this.receiver?.address || "address";
const desc = `The request with message_id "${request.message_id}" to "${address}" endpoint timed out. Please try again later.`;
const e = {
name: "OperationTimeoutError",
message: desc
};
return reject((0, import_errors.translate)(e));
}, timeoutInMs);
this._responsesMap.set(request.message_id, {
resolve,
reject,
cleanupBeforeResolveOrReject: () => {
if (aborter) aborter.removeEventListener("abort", onAbort);
if ((0, import_core_util.isDefined)(timer)) {
clearTimeout(timer);
}
}
});
import_log.logger.verbose("[%s] %s request sent: %O", this.connection.id, request.to || "$management");
this.sender.send(request);
});
}
/**
* Closes the sender, receiver link and the underlying session.
* @returns Promise<void>
*/
async close() {
await this.sender.close({ closeSession: false });
await this.receiver.close({ closeSession: false });
await this.session.close();
}
/**
* Removes the sender, receiver link and it's underlying session.
* @returns void
*/
remove() {
this.sender.remove();
this.receiver.remove();
this.session.remove();
}
/**
* Creates an amqp request/response link.
*
* @param connection - The amqp connection.
* @param senderOptions - Options that must be provided to create the sender link.
* @param receiverOptions - Options that must be provided to create the receiver link.
* @param createOptions - Optional parameters that can be used to affect this method's behavior.
* For example, `abortSignal` can be passed to allow cancelling an in-progress `create` invocation.
* @returns Promise<RequestResponseLink>
*/
static async create(connection, senderOptions, receiverOptions, createOptions = {}) {
const { abortSignal } = createOptions;
const session = await connection.createSession({ abortSignal });
const sender = await session.createSender({ ...senderOptions, abortSignal });
const receiver = await session.createReceiver({ ...receiverOptions, abortSignal });
import_log.logger.verbose(
"[%s] Successfully created the sender and receiver links on the same session.",
connection.id
);
return new RequestResponseLink(session, sender, receiver);
}
}
const getCodeDescriptionAndError = (props = {}) => {
return {
statusCode: props[import_constants.Constants.statusCode] || props.statusCode,
statusDescription: props[import_constants.Constants.statusDescription] || props.statusDescription,
errorCondition: props[import_constants.Constants.errorCondition] || props.errorCondition
};
};
function onMessageReceived(context, connectionId, responsesMap) {
const message = context.message;
if (!message) {
import_log.logger.verbose(
`[${connectionId}] "message" property on the EventContext is "undefined" which is unexpected, returning from the "onMessageReceived" handler without resolving or rejecting the promise upon encountering the message event.`
);
return;
}
const responseCorrelationId = message.correlation_id;
if (!responsesMap.has(responseCorrelationId)) {
import_log.logger.verbose(
`[${connectionId}] correlationId "${responseCorrelationId}" property on the response does not match with any of the "request-id"s in the map, returning from the "onMessageReceived" handler without resolving or rejecting the promise upon encountering the message event.`
);
return;
}
const promise = responsesMap.get(responseCorrelationId);
promise.cleanupBeforeResolveOrReject();
const deleteResult = responsesMap.delete(responseCorrelationId);
import_log.logger.verbose(
`[${connectionId}] Successfully deleted the response with id ${responseCorrelationId} from the map. Delete result - ${deleteResult}`
);
const info = getCodeDescriptionAndError(message.application_properties);
let error;
if (!info.statusCode) {
error = new Error(
`[${connectionId}] No statusCode in the "application_properties" in the returned response with correlation-id: ${responseCorrelationId}`
);
}
if (info.statusCode > 199 && info.statusCode < 300) {
import_log.logger.verbose(
`[${connectionId}] Resolving the response with correlation-id: ${responseCorrelationId}`
);
return promise.resolve(message);
}
if (!error) {
const condition = info.errorCondition || import_errors.ConditionStatusMapper[info.statusCode] || "amqp:internal-error";
error = (0, import_errors.translate)({
condition,
description: info.statusDescription
});
import_log.logger.warning(`${error?.name}: ${error?.message}`);
}
(0, import_log.logErrorStackTrace)(error);
return promise.reject(error);
}
function onSenderError(context, connectionId, responsesMap) {
if (context.sender) {
for (const [key, promise] of responsesMap.entries()) {
import_log.logger.verbose(
`[${connectionId}] Sender closed due to error when sending request with message_id "${key}"`
);
promise.cleanupBeforeResolveOrReject();
promise.reject(context.sender.error);
}
responsesMap.clear();
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
RequestResponseLink,
getCodeDescriptionAndError,
onMessageReceived
});
//# sourceMappingURL=requestResponseLink.js.map