UNPKG

@azure/core-amqp

Version:

Common library for amqp based azure sdks like @azure/event-hubs.

250 lines (249 loc) • 10.1 kB
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