@azure/core-amqp
Version:
Common library for amqp based azure sdks like @azure/event-hubs.
225 lines • 11.1 kB
JavaScript
// 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