UNPKG

@azure/core-amqp

Version:

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

225 lines • 11.1 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import { AbortError } from "@azure/abort-controller"; import { ReceiverEvents, SenderEvents, generate_uuid } from "rhea-promise"; import { logErrorStackTrace, logger } from "./log.js"; import { Constants } from "./util/constants.js"; import { RequestResponseLink } from "./requestResponseLink.js"; import { StandardAbortMessage } from "./util/constants.js"; import { defaultCancellableLock } from "./util/utils.js"; import { isError } from "@azure/core-util"; import { translate } from "./errors.js"; /** * Describes the EventHub/ServiceBus Cbs client that talks to the $cbs endpoint over AMQP connection. */ export class CbsClient { /** * @param connection - The AMQP connection. * @param connectionLock - A unique string (usually a guid) per connection. */ constructor(connection, connectionLock) { /** * CBS endpoint - "$cbs" */ this.endpoint = Constants.cbsEndpoint; /** * CBS replyTo - The receiver link name that the service should reply to. */ this.replyTo = `${Constants.cbsReplyTo}-${generate_uuid()}`; /** * The unique lock name per $cbs session per connection that is used to * acquire the lock for establishing a cbs session if one does not exist for an amqp connection. */ this.cbsLock = `${Constants.negotiateCbsKey}-${generate_uuid()}`; this.connection = connection; this.connectionLock = connectionLock; } /** * Creates a singleton instance of the CBS session if it hasn't been initialized previously on * the given connection. * @param options - Optional parameters that can be used to affect this method's behavior. * For example, `abortSignal` can be passed to allow cancelling an in-progress `init` invocation. * @returns Promise<void>. */ async init(options = {}) { const { abortSignal, timeoutInMs } = options; try { if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) { throw new AbortError(StandardAbortMessage); } // Acquire the lock and establish an amqp connection if it does not exist. if (!this.connection.isOpen()) { logger.verbose("The CBS client is trying to establish an AMQP connection."); await defaultCancellableLock.acquire(this.connectionLock, () => { return this.connection.open({ abortSignal }); }, { abortSignal: abortSignal, timeoutInMs: timeoutInMs }); } if (!this.isOpen()) { const rxOpt = { source: { address: this.endpoint, }, name: this.replyTo, onSessionError: (context) => { const id = context.connection.options.id; const ehError = translate(context.session.error); logger.verbose("[%s] An error occurred on the session for request/response links " + "for $cbs: %O", id, ehError); }, }; const srOpt = { target: { address: this.endpoint } }; logger.verbose("[%s] Creating sender/receiver links on a session for $cbs endpoint.", this.connection.id); this._cbsSenderReceiverLink = await RequestResponseLink.create(this.connection, srOpt, rxOpt, { abortSignal }); this._cbsSenderReceiverLink.sender.on(SenderEvents.senderError, (context) => { const id = context.connection.options.id; const ehError = translate(context.sender.error); logger.verbose("[%s] An error occurred on the cbs sender link.. %O", id, ehError); }); this._cbsSenderReceiverLink.receiver.on(ReceiverEvents.receiverError, (context) => { const id = context.connection.options.id; const ehError = translate(context.receiver.error); logger.verbose("[%s] An error occurred on the cbs receiver link.. %O", id, ehError); }); logger.verbose("[%s] Successfully created the cbs sender '%s' and receiver '%s' " + "links over cbs session.", this.connection.id, this._cbsSenderReceiverLink.sender.name, this._cbsSenderReceiverLink.receiver.name); } else { logger.verbose("[%s] CBS session is already present. Reusing the cbs sender '%s' " + "and receiver '%s' links over cbs session.", this.connection.id, this._cbsSenderReceiverLink.sender.name, this._cbsSenderReceiverLink.receiver.name); } } catch (err) { const translatedError = translate(err); logger.warning("[%s] An error occurred while establishing the cbs links: %s", this.connection.id, `${translatedError === null || translatedError === void 0 ? void 0 : translatedError.name}: ${translatedError === null || translatedError === void 0 ? void 0 : translatedError.message}`); logErrorStackTrace(translatedError); throw translatedError; } } /** * Negotiates the CBS claim with the EventHub/ServiceBus Service. * @param audience - The entity token audience for which the token is requested in one * of the following forms: * * - **ServiceBus** * - **Sender** * - `"sb://<yournamespace>.servicebus.windows.net/<queue-name>"` * - `"sb://<yournamespace>.servicebus.windows.net/<topic-name>"` * * - **Receiver** * - `"sb://<yournamespace>.servicebus.windows.net/<queue-name>"` * - `"sb://<yournamespace>.servicebus.windows.net/<topic-name>"` * * - **ManagementClient** * - `"sb://<your-namespace>.servicebus.windows.net/<queue-name>/$management"`. * - `"sb://<your-namespace>.servicebus.windows.net/<topic-name>/$management"`. * * - **EventHubs** * - **Sender** * - `"sb://<yournamespace>.servicebus.windows.net/<hubName>"` * - `"sb://<yournamespace>.servicebus.windows.net/<hubName>/Partitions/<partitionId>"`. * * - **Receiver** * - `"sb://<your-namespace>.servicebus.windows.net/<event-hub-name>/ConsumerGroups/<consumer-group-name>/Partitions/<partition-id>"`. * * - **ManagementClient** * - `"sb://<your-namespace>.servicebus.windows.net/<event-hub-name>/$management"`. * @param token - The token that needs to be sent in the put-token request. * @param tokenType - The type of token being used. For example, 'jwt' or 'servicebus.windows.net:sastoken'. * @param options - Optional parameters that can be used to affect this method's behavior. * For example, `abortSignal` can be passed to allow cancelling an in-progress `negotiateClaim` invocation. * @returns A Promise that resolves when $cbs authentication is successful * and rejects when an error occurs during $cbs authentication. */ async negotiateClaim(audience, token, tokenType, options = {}) { const { abortSignal, timeoutInMs } = options; try { if (abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted) { throw new AbortError(StandardAbortMessage); } if (!this._cbsSenderReceiverLink) { throw new Error("Attempted to negotiate a claim but the CBS link does not exist."); } const request = { body: token, message_id: generate_uuid(), reply_to: this.replyTo, to: this.endpoint, application_properties: { operation: Constants.operationPutToken, name: audience, type: tokenType, }, }; const responseMessage = await this._cbsSenderReceiverLink.sendRequest(request, { abortSignal, timeoutInMs, requestName: "negotiateClaim", }); logger.verbose("[%s] The CBS response is: %O", this.connection.id, responseMessage); return this._fromRheaMessageResponse(responseMessage); } catch (err) { logger.warning("[%s] An error occurred while negotiating the cbs claim: %s", this.connection.id, isError(err) ? `${err.name}: ${err.message}` : String(err)); logErrorStackTrace(err); throw err; } } /** * Closes the AMQP cbs session to the EventHub/ServiceBus for this client, * returning a promise that will be resolved when disconnection is completed. * @returns */ async close() { try { if (this.isOpen()) { const cbsLink = this._cbsSenderReceiverLink; this._cbsSenderReceiverLink = undefined; await cbsLink.close(); logger.verbose("[%s] Successfully closed the cbs session.", this.connection.id); } } catch (err) { const msg = `An error occurred while closing the cbs link: ${isError(err) && err.stack ? err.stack : JSON.stringify(err)}.`; logger.verbose("[%s] %s", this.connection.id, msg); throw new Error(msg); } } /** * Removes the AMQP cbs session to the EventHub/ServiceBus for this client, * @returns void */ remove() { try { if (this._cbsSenderReceiverLink) { const cbsLink = this._cbsSenderReceiverLink; this._cbsSenderReceiverLink = undefined; cbsLink.remove(); logger.verbose("[%s] Successfully removed the cbs session.", this.connection.id); } } catch (err) { const msg = `An error occurred while removing the cbs link: ${isError(err) && err.stack ? err.stack : JSON.stringify(err)}.`; logger.verbose("[%s] %s", this.connection.id, msg); throw new Error(msg); } } /** * Indicates whether the cbs sender receiver link is open or closed. * @returns `true` open, `false` closed. */ isOpen() { var _a; return Boolean((_a = this._cbsSenderReceiverLink) === null || _a === void 0 ? void 0 : _a.isOpen()); } _fromRheaMessageResponse(msg) { const cbsResponse = { correlationId: msg.correlation_id, statusCode: msg.application_properties ? msg.application_properties["status-code"] : "", statusDescription: msg.application_properties ? msg.application_properties["status-description"] : "", }; logger.verbose("[%s] The deserialized CBS response is: %o", this.connection.id, cbsResponse); return cbsResponse; } } //# sourceMappingURL=cbs.js.map