зеркало из
1
0
Форкнуть 0

Moving to amqp-common and updated structure of rhea-promise

This commit is contained in:
Amar Zavery 2018-06-27 20:00:51 -07:00
Родитель 5d643dac3e
Коммит df0e15445b
45 изменённых файлов: 1890 добавлений и 1291 удалений

7
.gitignore поставляемый
Просмотреть файл

@ -33,6 +33,10 @@ node_modules
# WebStorm
.idea
# coverage #
.nyc_output
coverage
# Mac OS #
.DS_Store
.DS_Store?
@ -51,4 +55,5 @@ typings/
dist/
output/
client/examples/js/lib/
processor/examples/js/lib/
processor/examples/js/lib/
typedoc

Просмотреть файл

@ -2,6 +2,7 @@ language: node_js
node_js:
- "8"
- "10"
before_install:
- cd client

Просмотреть файл

@ -15,6 +15,7 @@ async function main(): Promise<void> {
};
const delivery = await client.send(data);
console.log(">>> Sent the message successfully: ", delivery.id);
// await (Object.values((client as any)._context.senders)[0] as any).close();
// await client.close();
}

Просмотреть файл

Просмотреть файл

@ -2,7 +2,7 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as crypto from "crypto";
import { parseConnectionString, EventHubConnectionStringModel } from "../util/utils";
import { parseConnectionString, ServiceBusConnectionStringModel } from "../util/utils";
import { TokenInfo, TokenProvider, TokenType } from "./token";
/**
@ -11,17 +11,17 @@ import { TokenInfo, TokenProvider, TokenType } from "./token";
*/
export class SasTokenProvider implements TokenProvider {
/**
* @property {string} namespace - The namespace of the EventHub instance.
* @property {string} namespace - The namespace of the EventHub/ServiceBus instance.
*/
namespace: string;
/**
* @property {string} keyName - The name of the EventHub key.
* @property {string} keyName - The name of the EventHub/ServiceBus key.
*/
keyName: string;
/**
* @property {string} key - The secret value associated with the above EventHub key
* @property {string} key - The secret value associated with the above EventHub/ServiceBus key
*/
key: string;
/**
@ -36,9 +36,9 @@ export class SasTokenProvider implements TokenProvider {
/**
* Initializes a new isntance of SasTokenProvider
* @constructor
* @param {string} namespace - The namespace of the EventHub instance.
* @param {string} keyName - The name of the EventHub key.
* @param {string} key - The secret value associated with the above EventHub key
* @param {string} namespace - The namespace of the EventHub/ServiceBus instance.
* @param {string} keyName - The name of the EventHub/ServiceBus key.
* @param {string} key - The secret value associated with the above EventHub/ServiceBus key
*/
constructor(namespace: string, keyName: string, key: string, tokenValidTimeInSeconds?: number, tokenRenewalMarginInSeconds?: number) {
this.namespace = namespace;
@ -83,11 +83,11 @@ export class SasTokenProvider implements TokenProvider {
}
/**
* Creates a token provider from the EventHub connection string;
* @param {string} connectionString - The EventHub connection string
* Creates a token provider from the EventHub/ServiceBus connection string;
* @param {string} connectionString - The EventHub/ServiceBus connection string
*/
static fromConnectionString(connectionString: string): SasTokenProvider {
const parsed = parseConnectionString<EventHubConnectionStringModel>(connectionString);
const parsed = parseConnectionString<ServiceBusConnectionStringModel>(connectionString);
return new SasTokenProvider(parsed.Endpoint, parsed.SharedAccessKeyName, parsed.SharedAccessKey);
}
}

Просмотреть файл

Просмотреть файл

@ -2,21 +2,23 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
import { TokenInfo } from "./auth/token";
import { RequestResponseLink, createRequestResponseLink, sendRequest } from "./rpc";
import * as rheaPromise from "./rhea-promise";
import {
EventContext, ReceiverOptions, Message, SenderEvents, ReceiverEvents, Connection
} from "../rhea-promise";
import * as uuid from "uuid/v4";
import * as Constants from "./util/constants";
import * as debugModule from "debug";
import * as rpc from "./rpc";
import { Message } from ".";
import { ConnectionConfig } from "./connectionConfig";
import { translate } from "./errors";
import { defaultLock } from "./util/utils";
import { ConnectionContext } from "./connectionContext";
const debug = debugModule("azure:event-hubs:cbs");
import { RequestResponseLink } from "./requestResponseLink";
import { SenderOptions } from "rhea";
const debug = debugModule("azure:amqp-common:cbs");
/**
* @class CbsClient
* Describes the EventHub Cbs client that talks to the $cbs endopint over AMQP connection.
* Describes the EventHub/ServiceBus Cbs client that talks to the $cbs endopint over AMQP connection.
*/
export class CbsClient {
/**
@ -34,15 +36,42 @@ export class CbsClient {
*/
readonly cbsLock: string = `${Constants.negotiateCbsKey}-${uuid()}`;
/**
* @property {string} connectionLock The unqiue lock name per connection that is used to
* acquire the lock for establishing an amqp connection if one does not exist.
*/
readonly connectionLock: string = `${Constants.establishConnection}-${uuid()}`;
/**
* @property {ConnectionConfig} config The connection config.
*/
config: ConnectionConfig;
/**
* @property {string} pacakgeVersion The package version that will be set as a property of the
* connection.
*/
packageVersion: string;
/**
* @property {string} userAgent The useragent string.
*/
userAgent: string;
/**
* @property {Connection} connection The AMQP connection.
*/
connection?: Connection;
/**
* CBS sender, receiver on the same session.
*/
private _cbsSenderReceiverLink?: RequestResponseLink;
private _context: ConnectionContext;
constructor(context: ConnectionContext) {
this._context = context;
constructor(config: ConnectionConfig, packageVersion: string, userAgent?: string) {
this.config = config;
this.packageVersion = packageVersion;
this.userAgent = userAgent || "/js-amqp-client";
}
/**
@ -53,47 +82,47 @@ export class CbsClient {
try {
// Acquire the lock and establish an amqp connection if it does not exist.
if (!this._isConnectionOpen()) {
debug("[%s] The CBS client is trying to establish an AMQP connection.", this._context.connectionId);
debug("The CBS client is trying to establish an AMQP connection.");
const params: rpc.CreateConnectionPrameters = {
config: this._context.config,
userAgent: ConnectionContext.userAgent
config: this.config,
packageVersion: this.packageVersion,
userAgent: this.userAgent
};
this._context.connection = await defaultLock.acquire(this._context.connectionLock, () => { return rpc.open(params); });
this._context.connectionId = this._context.connection.options.id;
this.connection = await defaultLock.acquire(this.connectionLock, () => { return rpc.open(params); });
}
if (!this._cbsSenderReceiverLink) {
const rxOpt: rheaPromise.ReceiverOptions = {
if (!this._isCbsSenderReceiverLinkOpen()) {
const rxOpt: ReceiverOptions = {
source: {
address: this.endpoint
},
name: this.replyTo
};
this._cbsSenderReceiverLink = await createRequestResponseLink(this._context.connection, { target: { address: this.endpoint } }, rxOpt);
this._cbsSenderReceiverLink.sender.on("sender_error", (context: rheaPromise.EventContext) => {
const srOpt: SenderOptions = { target: { address: this.endpoint } };
this._cbsSenderReceiverLink = await RequestResponseLink.create(this.connection!, srOpt, rxOpt);
this._cbsSenderReceiverLink.sender.registerHandler(SenderEvents.senderError, (context: EventContext) => {
const ehError = translate(context.sender!.error!);
debug("An error occurred on the cbs sender link.. %O", ehError);
});
this._cbsSenderReceiverLink.receiver.on("receiver_error", (context: rheaPromise.EventContext) => {
this._cbsSenderReceiverLink.receiver.registerHandler(ReceiverEvents.receiverError, (context: EventContext) => {
const ehError = translate(context.receiver!.error!);
debug("An error occurred on the cbs receiver link.. %O", ehError);
});
debug("[%s] Successfully created the cbs sender '%s' and receiver '%s' links over cbs session.",
this._context.connectionId, this._cbsSenderReceiverLink.sender.name, this._cbsSenderReceiverLink.receiver.name);
this.connection!.id, this._cbsSenderReceiverLink.sender.name, this._cbsSenderReceiverLink.receiver.name);
} else {
debug("[%s] CBS session is already present. Reusing the cbs sender '%s' and receiver '%s' links over cbs session.",
this._context.connectionId, this._cbsSenderReceiverLink.sender.name, this._cbsSenderReceiverLink.receiver.name);
this.connection!.id, this._cbsSenderReceiverLink!.sender.name, this._cbsSenderReceiverLink!.receiver.name);
}
} catch (err) {
err = translate(err);
debug("[%s] An error occured while establishing the cbs links: %O",
this._context.connectionId, err);
debug("[%s] An error occured while establishing the cbs links: %O", this.connection!.id, err);
throw err;
}
}
/**
* Negotiates the CBS claim with the EventHub Service.
* Negotiates the CBS claim with the EventHub/ServiceBus Service.
* @param {string} audience The audience for which the token is requested.
* @param {TokenInfo} tokenObject The token object that needs to be sent in the put-token request.
* @return {Promise<any>} Returns a Promise that resolves when $cbs authentication is successful
@ -112,40 +141,38 @@ export class CbsClient {
type: tokenObject.tokenType
}
};
const response = await sendRequest(this._context.connection, this._cbsSenderReceiverLink!, request);
const response = await this._cbsSenderReceiverLink!.sendRequest(request);
return response;
} catch (err) {
debug("[%s]An error occurred while negotating the cbs claim: %O", this._context.connectionId, err);
debug("[%s] An error occurred while negotating the cbs claim: %O", this.connection!.id, err);
throw err;
}
}
/**
* Closes the AMQP cbs session to the Event Hub for this client,
* Closes the AMQP cbs session to the EventHub/ServiceBus for this client,
* returning a promise that will be resolved when disconnection is completed.
* @return {Promise<void>}
*/
async close(): Promise<void> {
try {
if (this._cbsSenderReceiverLink) {
await rheaPromise.closeSession(this._cbsSenderReceiverLink.session);
debug("Successfully closed the cbs session.");
if (this._isCbsSenderReceiverLinkOpen()) {
await this._cbsSenderReceiverLink!.close();
debug("[%s] Successfully closed the cbs session.", this.connection!.id);
this._cbsSenderReceiverLink = undefined;
}
} catch (err) {
const msg = `An error occurred while closing the cbs session: ${JSON.stringify(err)} `;
debug(msg);
const msg = `An error occurred while closing the cbs link: ${err.stack || JSON.stringify(err)}.`;
debug("[%s] %s", this.connection!.id, msg);
throw new Error(msg);
}
}
private _isCbsSenderReceiverLinkOpen(): boolean {
return this._cbsSenderReceiverLink! && this._cbsSenderReceiverLink!.isOpen();
}
private _isConnectionOpen(): boolean {
let result: boolean = false;
if (this._context && this._context.connection) {
if (this._context.connection.is_open && this._context.connection.is_open()) {
result = true;
}
}
return result;
return this.connection! && this.connection!.isOpen();
}
}

Просмотреть файл

@ -1,11 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import { parseConnectionString, EventHubConnectionStringModel } from "./util/utils";
import { parseConnectionString, ServiceBusConnectionStringModel } from "./util/utils";
export interface ConnectionConfigOptions {
isEntityPathRequired?: boolean;
}
export interface ConnectionConfig {
/**
* @property {string} endpoint - The service bus endpoint "sb://<yournamespace>.servicebus.windows.net/".
* @property {string} endpoint - The service bus endpoint
* "sb://<yournamespace>.servicebus.windows.net/".
*/
endpoint: string;
/**
@ -17,7 +22,8 @@ export interface ConnectionConfig {
*/
connectionString: string;
/**
* @property {string} entityPath - The name/path of the entity (hub name) to which the connection needs to happen.
* @property {string} entityPath - The name/path of the entity (hub name) to which the
* connection needs to happen.
*/
entityPath?: string;
/**
@ -33,16 +39,19 @@ export interface ConnectionConfig {
export namespace ConnectionConfig {
/**
* Creates the connection config.
* @param {string} connectionString - The event hub connection string
* @param {string} [path] - The name/path of the entity (hub name) to which the connection needs to happen
* @param {string} connectionString - The connection string for a given service like
* EventHub/ServiceBus.
* @param {string} [path] - The name/path of the entity (hub name) to which the
* connection needs to happen.
*/
export function create(connectionString: string, path?: string): ConnectionConfig {
if (!connectionString || (connectionString && typeof connectionString !== "string")) {
throw new Error("'connectionString' is a required parameter and must be of type: 'string'.");
}
const parsedCS = parseConnectionString<EventHubConnectionStringModel>(connectionString);
const parsedCS = parseConnectionString<ServiceBusConnectionStringModel>(connectionString);
if (!path && !parsedCS.EntityPath) {
throw new Error(`Either provide "path" or the "connectionString": "${connectionString}", must contain EntityPath="<path-to-the-entity>".`);
throw new Error(`Either provide "path" or the "connectionString": "${connectionString}", ` +
`must contain EntityPath="<path-to-the-entity>".`);
}
const result: ConnectionConfig = {
connectionString: connectionString,
@ -59,21 +68,25 @@ export namespace ConnectionConfig {
* Validates the properties of connection config.
* @param {ConnectionConfig} config The connection config to be validated.
*/
export function validate(config: ConnectionConfig): void {
export function validate(config: ConnectionConfig, options?: ConnectionConfigOptions): void {
if (!options) options = {};
if (!config || (config && typeof config !== "object")) {
throw new Error("'config' is a required parameter and must be of type: 'object'.");
}
if (!config.endpoint || (config.endpoint && typeof config.endpoint !== "string")) {
throw new Error("'endpoint' is a required property of the ConnectionConfig.");
throw new Error("'endpoint' is a required property of ConnectionConfig.");
}
if (!config.entityPath || (config.entityPath && typeof config.entityPath !== "string")) {
throw new Error("'entityPath' is a required property of the ConnectionConfig.");
if (config.entityPath && typeof config.entityPath !== "string") {
throw new Error("'entityPath' must be of type 'string'.");
}
if (options.isEntityPathRequired && !config.entityPath) {
throw new Error("'entityPath' is a required property of ConnectionConfig.");
}
if (!config.sharedAccessKeyName || (config.sharedAccessKeyName && typeof config.sharedAccessKeyName !== "string")) {
throw new Error("'sharedAccessKeyName' is a required property of the ConnectionConfig.");
throw new Error("'sharedAccessKeyName' is a required property of ConnectionConfig.");
}
if (!config.sharedAccessKey || (config.sharedAccessKey && typeof config.sharedAccessKey !== "string")) {
throw new Error("'sharedAccessKey' is a required property of the ConnectionConfig.");
throw new Error("'sharedAccessKey' is a required property of ConnectionConfig.");
}
}
}

Просмотреть файл

@ -5,7 +5,7 @@ import * as rhea from "rhea";
import * as debugModule from "debug";
const isBuffer = require("is-buffer");
const debug = debugModule("azure:event-hubs:datatransformer");
const debug = debugModule("azure:amqp-common:datatransformer");
export interface DataTransformer {
/**
@ -29,6 +29,7 @@ export class DefaultDataTransformer implements DataTransformer {
/**
* A function that takes the body property from an EventData object
* and returns an encoded body (some form of AMQP type).
*
* @param {*} body The AMQP message body
* @return {DataSection} encodedBody - The encoded AMQP message body as an AMQP Data type
* (data section in rhea terms). Section object with following properties:

Просмотреть файл

@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import { AmqpError, AmqpResponseStatusCode } from "../lib/rhea-promise";
import { AmqpResponseStatusCode, isAmqpError } from "../rhea-promise";
import { AmqpError } from "rhea";
/**
* Maps the conditions to the numeric AMQP Response status codes.
* @enum {ConditionStatusMapper}
@ -12,6 +12,7 @@ export enum ConditionStatusMapper {
"amqp:not-found" = AmqpResponseStatusCode.NotFound,
"amqp:not-implemented" = AmqpResponseStatusCode.NotImplemented,
"com.microsoft:entity-already-exists" = AmqpResponseStatusCode.Conflict,
"com.microsoft:message-lock-lost" = AmqpResponseStatusCode.Gone,
"com.microsoft:session-lock-lost" = AmqpResponseStatusCode.Gone,
"com.microsoft:no-matching-subscription" = AmqpResponseStatusCode.InternalServerError,
"amqp:link:message-size-exceeded" = AmqpResponseStatusCode.Forbidden,
@ -26,8 +27,7 @@ export enum ConditionStatusMapper {
"amqp:link:stolen" = AmqpResponseStatusCode.Gone,
"amqp:not-allowed" = AmqpResponseStatusCode.BadRequest,
"amqp:unauthorized-access" = AmqpResponseStatusCode.Unauthorized,
"amqp:resource-limit-exceeded" = AmqpResponseStatusCode.Forbidden,
"com.microsoft:message-lock-lost" = AmqpResponseStatusCode.Gone,
"amqp:resource-limit-exceeded" = AmqpResponseStatusCode.Forbidden
}
/**
@ -35,6 +35,22 @@ export enum ConditionStatusMapper {
* @enum {ConditionErrorNameMapper}
*/
export enum ConditionErrorNameMapper {
/**
* Error is thrown when trying to access/connect to a disabled messaging entity.
*/
"com.microsoft:entity-disabled" = "MessagingEntityDisabledError",
/**
* Error is thrown when the lock on the message is lost.
*/
"com.microsoft:message-lock-lost" = "MessageLockLostError",
/**
* Error is thrown when the lock on the Azure ServiceBus session is lost.
*/
"com.microsoft:session-lock-lost" = "SessionLockLostError",
/**
* Error is thrown when the Azure ServiceBus session cannot be locked.
*/
"com.microsoft:session-cannot-be-locked" = "SessionCannotBeLockedError",
/**
* Error is thrown when an internal server error occured. You may have found a bug?
*/
@ -42,7 +58,7 @@ export enum ConditionErrorNameMapper {
/**
* Error for signaling general communication errors related to messaging operations.
*/
"amqp:not-found" = "EventHubsCommunicationError",
"amqp:not-found" = "ServiceCommunicationError",
/**
* Error is thrown when a feature is not implemented yet but the placeholder is present.
*/
@ -52,7 +68,7 @@ export enum ConditionErrorNameMapper {
*/
"amqp:not-allowed" = "InvalidOperationError",
/**
* Error is thrown the the Azure Event Hub quota has been exceeded.
* Error is thrown the the Azure EventHub/ServiceBus quota has been exceeded.
* Quotas are reset periodically, this operation will have to wait until then.
* The messaging entity has reached its maximum allowable size.
* This can happen if the maximum number of receivers (which is 5) has already
@ -63,6 +79,10 @@ export enum ConditionErrorNameMapper {
* Error is thrown when the connection parameters are wrong and the server refused the connection.
*/
"amqp:unauthorized-access" = "UnauthorizedError",
/**
* Error is thrown when the connection parameters are wrong and the server refused the connection.
*/
"com.microsoft:auth-failed" = "UnauthorizedError",
/**
* Error is thrown when the service is unavailable. The operation should be retried.
*/
@ -75,6 +95,10 @@ export enum ConditionErrorNameMapper {
* Error is thrown when a condition that should have been met in order to execute an operation was not.
*/
"amqp:precondition-failed" = "PreconditionFailedError",
/**
* Error is thrown when a condition that should have been met in order to execute an operation was not.
*/
"com.microsoft:precondition-failed" = "PreconditionFailedError",
/**
* Error is thrown when data could not be decoded.
*/
@ -167,6 +191,22 @@ export enum ConditionErrorNameMapper {
* @enum {ErrorNameConditionMapper}
*/
export enum ErrorNameConditionMapper {
/**
* Error is thrown when trying to access/connect to a disabled messaging entity.
*/
MessagingEntityDisabledError = "com.microsoft:entity-disabled",
/**
* Error is thrown when the lock on the message is lost.
*/
MessageLockLostError = "com.microsoft:message-lock-lost",
/**
* Error is thrown when the lock on the Azure ServiceBus session is lost.
*/
SessionLockLostError = "com.microsoft:session-lock-lost",
/**
* Error is thrown when the Azure ServiceBus session cannot be locked.
*/
SessionCannotBeLockedError = "com.microsoft:session-cannot-be-locked",
/**
* Error is thrown when an internal server error occured. You may have found a bug?
*/
@ -174,7 +214,7 @@ export enum ErrorNameConditionMapper {
/**
* Error for signaling general communication errors related to messaging operations.
*/
EventHubsCommunicationError = "amqp:not-found",
ServiceCommunicationError = "amqp:not-found",
/**
* Error is thrown when a feature is not implemented yet but the placeholder is present.
*/
@ -184,7 +224,7 @@ export enum ErrorNameConditionMapper {
*/
InvalidOperationError = "amqp:not-allowed",
/**
* Error is thrown the the Azure Event Hub quota has been exceeded.
* Error is thrown the the Azure EventHub/ServiceBus quota has been exceeded.
* Quotas are reset periodically, this operation will have to wait until then.
* The messaging entity has reached its maximum allowable size.
* This can happen if the maximum number of receivers (which is 5) has already
@ -295,19 +335,19 @@ export enum ErrorNameConditionMapper {
}
/**
* Describes the base class for an EventHub Error.
* @class {EventHubsError}
* Describes the base class for Messaging Error.
* @class {MessagingError}
* @extends Error
*/
export class EventHubsError extends Error {
export class MessagingError extends Error {
/**
* @property {string} [condition] The error condition.
*/
condition?: string;
/**
* @property {string} name The error name. Default value: "EventHubsError".
* @property {string} name The error name. Default value: "MessagingError".
*/
name: string = "EventHubsError";
name: string = "MessagingError";
/**
* @property {boolean} translated Has the error been translated. Default: true.
*/
@ -330,44 +370,24 @@ export class EventHubsError extends Error {
}
/**
* Determines whether the given error object is like an AmqpError object.
* @param err The AmqpError object
*/
function isAmqpError(err: any): boolean {
if (!err || typeof err !== "object") {
throw new Error("err is a required parameter and must be of type 'object'.");
}
let result: boolean = false;
if (((err.condition && typeof err.condition === "string") && (err.description && typeof err.description === "string"))
|| (err.value && Array.isArray(err.value))
|| (err.constructor && err.constructor.name === "c")) {
result = true;
}
return result;
}
/**
* Translates the AQMP error received at the protocol layer or a generic Error into an EventHubsError.
* Translates the AQMP error received at the protocol layer or a generic Error into an MessagingError.
*
* @param {AmqpError} err The amqp error that was received.
* @returns {EventHubsError} EventHubsError object.
* @returns {MessagingError} MessagingError object.
*/
export function translate(err: AmqpError | Error): EventHubsError {
if ((err as EventHubsError).translated) { // already translated
return err as EventHubsError;
export function translate(err: AmqpError | Error): MessagingError {
if ((err as MessagingError).translated) { // already translated
return err as MessagingError;
} else if (isAmqpError(err)) { // translate
const condition = (err as AmqpError).condition;
const description = (err as AmqpError).description as string;
const error = new EventHubsError(description);
const error = new MessagingError(description);
error.info = (err as AmqpError).info;
error.condition = condition;
if (condition) {
if (condition === "com.microsoft:precondition-failed") {
error.name = "PreconditionFailedError";
} else {
error.name = ConditionErrorNameMapper[condition as any] || "EventHubsError";
}
error.name = ConditionErrorNameMapper[condition as any];
}
if (!error.name) error.name = "MessagingError";
if (description &&
(description.includes("status-code: 404") ||
description.match(/The messaging entity .* could not be found.*/i) !== null)) {
@ -380,8 +400,8 @@ export function translate(err: AmqpError | Error): EventHubsError {
}
return error;
} else {
// Translate a generic error into EventHubsError.
const error = new EventHubsError((err as Error).message);
// Translate a generic error into MessagingError.
const error = new MessagingError((err as Error).message);
return error;
}
}

Просмотреть файл

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
export { Dictionary, Message as AmqpMessage } from "../rhea-promise";
export { ConnectionConfig } from "./connectionConfig";
export {
MessagingError, ErrorNameConditionMapper, ConditionStatusMapper, ConditionErrorNameMapper, translate
} from "./errors";
export { RequestResponseLink } from "./requestResponseLink";
export {
CreateConnectionPrameters, open, ServiceBusMessageAnnotations,
ServiceBusDeliveryAnnotations, EventHubDeliveryAnnotations, EventHubMessageAnnotations
} from "./rpc";
export { retry } from "./retry";
export { DataTransformer, DefaultDataTransformer } from "./dataTransformer";
export { TokenType, TokenProvider, TokenInfo } from "./auth/token";
export { SasTokenProvider } from "./auth/sas";
export { AadTokenProvider } from "./auth/aad";
export { CbsClient } from "./cbs";
import * as Constants from "./util/constants";
export { Constants };
export { MessageHeader } from "./messageHeader";
export { MessageProperties } from "./messageProperties";
export {
delay, Timeout, EventHubConnectionStringModel, executePromisesSequentially,
parseConnectionString, IotHubConnectionStringModel, StorageConnectionStringModel, defaultLock,
Func, ParsedOutput, getNewAsyncLock, AsyncLockOptions, ServiceBusConnectionStringModel,
isIotHubConnectionString
} from "./util/utils";

Просмотреть файл

@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import { MessageHeader as AmqpMessageHeader } from "../rhea-promise";
import * as debugModule from "debug";
const debug = debugModule("azure:amqp-common:messageHeader");
/**
* Describes the defined set of standard header properties of the message.
* @interface MessageHeader
*/
export interface MessageHeader {
/**
* @property {boolean} [firstAcquirer] If this value is true, then this message has not been
* acquired by any other link. Ifthis value is false, then this message MAY have previously
* been acquired by another link or links.
*/
firstAcquirer?: boolean;
/**
* @property {number} [deliveryCount] The number of prior unsuccessful delivery attempts.
*/
deliveryCount?: number;
/**
* @property {number} [ttl] time to live in ms.
*/
ttl?: number;
/**
* @property {boolean} [durable] Specifies durability requirements.
*/
durable?: boolean;
/**
* @property {number} [priority] The relative message priority. Higher numbers indicate higher
* priority messages.
*/
priority?: number;
}
export namespace MessageHeader {
export function toAmqpMessageHeader(props: MessageHeader): AmqpMessageHeader {
const amqpHeader: AmqpMessageHeader = {
delivery_count: props.deliveryCount,
durable: props.durable,
first_acquirer: props.firstAcquirer,
priority: props.priority,
ttl: props.ttl
};
debug("To AmqpMessageHeader: %O", amqpHeader);
return amqpHeader;
}
export function fromAmqpMessageProperties(props: AmqpMessageHeader): MessageHeader {
const msgHeader: MessageHeader = {
deliveryCount: props.delivery_count,
durable: props.durable,
firstAcquirer: props.first_acquirer,
priority: props.priority,
ttl: props.ttl
};
debug("From AmqpMessageHeader: %O", msgHeader);
return msgHeader;
}
}

Просмотреть файл

@ -0,0 +1,111 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import { MessageProperties as AmqpMessageProperties } from "../rhea-promise";
import * as debugModule from "debug";
const debug = debugModule("azure:amqp-common:messageProperties");
/**
* Describes the defined set of standard properties of the message.
* @interface MessageProperties
*/
export interface MessageProperties {
/**
* @property {string | number | Buffer} [messageId] The application message identifier that uniquely idenitifes a message.
* The user is responsible for making sure that this is unique in the given context. Guids usually make a good fit.
*/
messageId?: string | number | Buffer;
/**
* @property {string} [replyTo] The address of the node to send replies to.
*/
replyTo?: string;
/**
* @property {string} [to] The address of the node the message is destined for.
*/
to?: string;
/**
* @property {string | number | Buffer} [correlationId] The id that can be used to mark or identify messages between clients.
*/
correlationId?: string | number | Buffer;
/**
* @property {string} [contentType] MIME type for the message.
*/
contentType?: string;
/**
* @property {string} [contentEncoding] The content-encoding property is used as a modifier to the content-type.
* When present, its valueindicates what additional content encodings have been applied to the application-data.
*/
contentEncoding?: string;
/**
* @property {number} [absoluteExpiryTime] The time when this message is considered expired.
*/
absoluteExpiryTime?: number;
/**
* @property {number} [creationTime] The time this message was created.
*/
creationTime?: number;
/**
* @property {string} [groupId] The group this message belongs to.
*/
groupId?: string;
/**
* @property {number} [groupSequence] The sequence number of this message with its group.
*/
groupSequence?: number;
/**
* @property {string} [replyToGroupId] The group the reply message belongs to.
*/
replyToGroupId?: string;
/**
* @property {string} [subject] A common field for summary information about the message
* content and purpose.
*/
subject?: string;
/**
* @property {string} [userId] The identity of the user responsible for producing the message.
*/
userId?: string;
}
export namespace MessageProperties {
export function toAmqpMessageProperties(props: MessageProperties): AmqpMessageProperties {
const amqpProperties: AmqpMessageProperties = {
absolute_expiry_time: props.absoluteExpiryTime,
content_encoding: props.contentEncoding,
content_type: props.contentType,
correlation_id: props.correlationId,
creation_time: props.creationTime,
group_id: props.groupId,
group_sequence: props.groupSequence,
message_id: props.messageId,
reply_to: props.replyTo,
reply_to_group_id: props.replyToGroupId,
subject: props.subject,
to: props.to,
user_id: props.userId
};
debug("To AmqpMessageProperties: %O", amqpProperties);
return amqpProperties;
}
export function fromAmqpMessageProperties(props: AmqpMessageProperties): MessageProperties {
const msgProperties: MessageProperties = {
absoluteExpiryTime: props.absolute_expiry_time,
contentEncoding: props.content_encoding,
contentType: props.content_type,
correlationId: props.correlation_id,
creationTime: props.creation_time,
groupId: props.group_id,
groupSequence: props.group_sequence,
messageId: props.message_id,
replyTo: props.reply_to,
replyToGroupId: props.reply_to_group_id,
subject: props.subject,
to: props.to,
userId: props.user_id
};
debug("From AmqpMessageProperties: %O", msgProperties);
return msgProperties;
}
}

Просмотреть файл

@ -0,0 +1,122 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as uuid from "uuid/v4";
import * as Constants from "./util/constants";
import * as debugModule from "debug";
import { retry } from "./retry";
import {
Session, Connection, Sender, Receiver, Message, EventContext, AmqpError, ReqResLink,
SenderOptions, ReceiverOptions, ReceiverEvents
} from "../rhea-promise";
import { translate, ConditionStatusMapper } from "./errors";
const debug = debugModule("azure:amqp-common:reqreslink");
export class RequestResponseLink implements ReqResLink {
constructor(public session: Session, public sender: Sender, public receiver: Receiver) {
this.session = session;
this.sender = sender;
this.receiver = receiver;
}
get connection(): Connection {
return this.session.connection;
}
isOpen(): boolean {
return this.session.isOpen() && this.sender.isOpen() && this.receiver.isOpen();
}
sendRequest<T>(request: Message, timeoutInSeconds?: number): Promise<T> {
if (!request) {
throw new Error("request is a required parameter and must be of type 'object'.");
}
if (!request.message_id) request.message_id = uuid();
if (!timeoutInSeconds) {
timeoutInSeconds = 10;
}
const sendRequestPromise: Promise<T> = new Promise((resolve: any, reject: any) => {
let waitTimer: any;
let timeOver: boolean = false;
const messageCallback = (context: EventContext) => {
// remove the event listener as this will be registered next time when someone makes a request.
this.receiver.removeHandler(ReceiverEvents.message, messageCallback);
const code: number = context.message!.application_properties![Constants.statusCode];
const desc: string = context.message!.application_properties![Constants.statusDescription];
const errorCondition: string | undefined = context.message!.application_properties![Constants.errorCondition];
const responseCorrelationId = context.message!.correlation_id;
debug("[%s] %s response: ", this.connection.id, request.to || "$management", context.message);
if (code > 199 && code < 300) {
if (request.message_id === responseCorrelationId || request.correlation_id === responseCorrelationId) {
if (!timeOver) {
clearTimeout(waitTimer);
}
debug("[%s] request-messageId | '%s' == '%s' | response-correlationId.",
this.connection.id, request.message_id, responseCorrelationId);
return resolve(context.message!.body);
} else {
debug("[%s] request-messageId | '%s' != '%s' | response-correlationId. " +
"Hence dropping this response and waiting for the next one.",
this.connection.id, request.message_id, responseCorrelationId);
}
} else {
const condition = errorCondition || ConditionStatusMapper[code] || "amqp:internal-error";
const e: AmqpError = {
condition: condition,
description: desc
};
return reject(translate(e));
}
};
const actionAfterTimeout = () => {
timeOver = true;
this.receiver.removeHandler(ReceiverEvents.message, messageCallback);
const address = this.receiver.address || "address";
const desc: string = `The request with message_id "${request.message_id}" to "${address}" ` +
`endpoint timed out. Please try again later.`;
const e: AmqpError = {
condition: ConditionStatusMapper[408],
description: desc
};
return reject(translate(e));
};
this.receiver.registerHandler(ReceiverEvents.message, messageCallback);
waitTimer = setTimeout(actionAfterTimeout, timeoutInSeconds! * 1000);
debug("[%s] %s request sent: %O", this.connection.id, request.to || "$managment", request);
this.sender.send(request);
});
return retry<T>(() => sendRequestPromise);
}
async close(): Promise<void> {
await this.sender.close();
await this.receiver.close();
await this.session.close();
}
static async create(connection: Connection, senderOptions: SenderOptions, receiverOptions: ReceiverOptions): Promise<RequestResponseLink> {
if (!connection) {
throw new Error(`Please provide a connection to create the sender/receiver link on the same session.`);
}
if (!senderOptions) {
throw new Error(`Please provide sender options.`);
}
if (!receiverOptions) {
throw new Error(`Please provide receiver options.`);
}
const session = await connection.createSession();
const sender = await session.createSender(senderOptions);
const receiver = await session.createReceiver(receiverOptions);
debug("[%s] Successfully created the sender and receiver links on the same session.", connection.id);
return new RequestResponseLink(session, sender, receiver);
}
}

Просмотреть файл

@ -1,10 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import { translate, EventHubsError } from "./errors";
import { translate, MessagingError } from "./errors";
import { delay } from ".";
import * as debugModule from "debug";
const debug = debugModule("azure:event-hubs:retry");
const debug = debugModule("azure:amqp-common:retry");
function isDelivery(obj: any): boolean {
let result: boolean = false;
@ -40,7 +40,7 @@ export async function retry<T>(operation: () => Promise<T>, times?: number, dela
if (!times) times = 3;
if (!delayInSeconds) delayInSeconds = 15;
let lastError: EventHubsError | undefined;
let lastError: MessagingError | undefined;
let result: any;
let success = false;
for (let i = 0; i < times; i++) {

Просмотреть файл

@ -0,0 +1,166 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as os from "os";
import * as process from "process";
import * as debugModule from "debug";
import { defaultLock } from "./util/utils";
import { ConnectionOptions, Connection, DeliveryAnnotations, MessageAnnotations } from "../rhea-promise";
import * as Constants from "./util/constants";
import { ConnectionConfig } from ".";
const debug = debugModule("azure:amqp-common:rpc");
/**
* Describes the delivery annotations.
* @interface
*/
export interface EventHubDeliveryAnnotations extends DeliveryAnnotations {
/**
* @property {string} [last_enqueued_offset] The offset of the last event.
*/
last_enqueued_offset?: string;
/**
* @property {number} [last_enqueued_sequence_number] The sequence number of the last event.
*/
last_enqueued_sequence_number?: number;
/**
* @property {number} [last_enqueued_time_utc] The enqueued time of the last event.
*/
last_enqueued_time_utc?: number;
/**
* @property {number} [runtime_info_retrieval_time_utc] The retrieval time of the last event.
*/
runtime_info_retrieval_time_utc?: number;
/**
* @property {string} Any unknown delivery annotations.
*/
[x: string]: any;
}
/**
* Describes the delivery annotations.
* @interface
*/
export interface ServiceBusDeliveryAnnotations extends DeliveryAnnotations {
/**
* @property {string} [last_enqueued_offset] The offset of the last event.
*/
last_enqueued_offset?: string;
/**
* @property {number} [last_enqueued_sequence_number] The sequence number of the last event.
*/
last_enqueued_sequence_number?: number;
/**
* @property {number} [last_enqueued_time_utc] The enqueued time of the last event.
*/
last_enqueued_time_utc?: number;
/**
* @property {number} [runtime_info_retrieval_time_utc] The retrieval time of the last event.
*/
runtime_info_retrieval_time_utc?: number;
/**
* @property {string} Any unknown delivery annotations.
*/
[x: string]: any;
}
/**
* Map containing message attributes that will be held in the message header.
*/
export interface EventHubMessageAnnotations extends MessageAnnotations {
/**
* @property {string | null} [x-opt-partition-key] Annotation for the partition key set for the event.
*/
"x-opt-partition-key"?: string | null;
/**
* @property {number} [x-opt-sequence-number] Annontation for the sequence number of the event.
*/
"x-opt-sequence-number"?: number;
/**
* @property {number} [x-opt-enqueued-time] Annotation for the enqueued time of the event.
*/
"x-opt-enqueued-time"?: number;
/**
* @property {string} [x-opt-offset] Annotation for the offset of the event.
*/
"x-opt-offset"?: string;
/**
* @property {any} Any other annotation that can be added to the message.
*/
[x: string]: any;
}
/**
* Map containing message attributes that will be held in the message header.
*/
export interface ServiceBusMessageAnnotations extends MessageAnnotations {
/**
* @property {string | null} [x-opt-partition-key] Annotation for the partition key set for the event.
*/
"x-opt-partition-key"?: string | null;
/**
* @property {number} [x-opt-sequence-number] Annontation for the sequence number of the event.
*/
"x-opt-sequence-number"?: number;
/**
* @property {number} [x-opt-enqueued-time] Annotation for the enqueued time of the event.
*/
"x-opt-enqueued-time"?: number;
/**
* @property {string} [x-opt-offset] Annotation for the offset of the event.
*/
"x-opt-offset"?: string;
/**
* @property {string} [x-opt-locked-until] Annotation for the message being locked until.
*/
"x-opt-locked-until"?: Date | number;
}
export interface CreateConnectionPrameters {
config: ConnectionConfig;
userAgent: string;
packageVersion: string;
useSaslPlain?: boolean;
}
/**
* Opens the AMQP connection to the EventHub/ServiceBus for this client, returning a promise
* that will be resolved when the connection is completed.
*
* @param {ConnectionContext} context The connection context.
* @param {boolean} [useSaslPlain] True for using sasl plain mode for authentication, false otherwise.
* @returns {Promise<Connection>} The Connection object.
*/
export async function open(params: CreateConnectionPrameters): Promise<Connection> {
try {
return await defaultLock.acquire("connect", () => { return _open(params); });
} catch (err) {
debug(err);
throw err;
}
}
async function _open(params: CreateConnectionPrameters): Promise<Connection> {
const connectOptions: ConnectionOptions = {
transport: Constants.TLS,
host: params.config.host,
hostname: params.config.host,
username: params.config.sharedAccessKeyName,
port: 5671,
reconnect_limit: Constants.reconnectLimit,
properties: {
product: "MSJSClient",
version: params.packageVersion,
platform: `(${os.arch()}-${os.type()}-${os.release()})`,
framework: `Node/${process.version}`,
"user-agent": params.userAgent
}
};
if (params.useSaslPlain) {
connectOptions.password = params.config.sharedAccessKey;
}
debug("Dialing the amqp connection with options.", connectOptions);
const connection = await new Connection(connectOptions).open();
debug("Successfully established the amqp connection '%s'.", connection.id);
return connection;
}

Просмотреть файл

@ -0,0 +1,69 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
export const partitionKey = "x-opt-partition-key";
export const sequenceNumber = "x-opt-sequence-number";
export const enqueueSequenceNumber = "x-opt-enqueue-sequence-number";
export const enqueuedTime = "x-opt-enqueued-time";
export const scheduledEnqueueTime = "x-opt-scheduled-enqueue-time";
export const offset = "x-opt-offset";
export const lockedUntil = "x-opt-locked-until";
export const partitionIdName = "x-opt-partition-id";
export const publisher = "x-opt-publisher-name";
export const viaPartitionKey = "x-opt-via-partition-key";
export const deadLetterSource = "x-opt-deadletter-source";
export const enqueuedTimeAnnotation = `amqp.annotation.${enqueuedTime}`;
export const offsetAnnotation = `amqp.annotation.${offset}`;
export const sequenceNumberAnnotation = `amqp.annotation.${sequenceNumber}`;
export const guidSize = 16;
export const message = "message";
export const error = "error";
export const statusCode = "status-code";
export const statusDescription = "status-description";
export const errorCondition = "error-condition";
export const management = "$management";
export const partition = "partition";
export const partitionId = "partitionId";
export const readOperation = "READ";
export const TLS = "tls";
export const establishConnection = "establishConnection";
export const defaultConsumerGroup = "$default";
export const eventHub = "eventhub";
export const cbsEndpoint = "$cbs";
export const cbsReplyTo = "cbs";
export const operationPutToken = "put-token";
export const aadEventHubsAudience = "https://eventhubs.azure.net/";
export const maxUserAgentLength = 128;
export const vendorString = "com.microsoft";
export const attachEpoch = `${vendorString}:epoch`;
export const receiverIdentifierName = `${vendorString}:receiver-name`;
export const enableReceiverRuntimeMetricName = `${vendorString}:enable-receiver-runtime-metric`;
export const timespan = `${vendorString}:timespan`;
export const uri = `${vendorString}:uri`;
export const dateTimeOffset = `${vendorString}:datetime-offset`;
export const sessionFilterName = `${vendorString}:session-filter`;
export const receiverError = "receiver_error";
export const senderError = "sender_error";
export const sessionError = "session_error";
export const connectionError = "connection_error";
export const defaultOperationTimeoutInSeconds = 60;
export const managementRequestKey = "managementRequest";
export const negotiateCbsKey = "negotiateCbs";
export const negotiateClaim = "negotiateClaim";
export const ensureContainerAndBlob = "ensureContainerAndBlob";
export const defaultPrefetchCount = 1000;
export const reconnectLimit = 100;
export const maxMessageIdLength = 128;
export const maxPartitionKeyLength = 128;
export const maxSessionIdLength = 128;
export const pathDelimiter = "/";
export const ruleNameMaximumLength = 50;
export const maximumSqlFilterStatementLength = 1024;
export const maximumSqlRuleActionStatementLength = 1024;
export const maxDeadLetterReasonLength = 4096;
// https://stackoverflow.com/questions/11526504/minimum-and-maximum-date for js
// However we are setting this to the TimeSpan.MaxValue of C#.
export const maxDurationValue = 922337203685477;
export const minDurationValue = -922337203685477;
// https://github.com/Azure/azure-amqp/blob/master/Microsoft.Azure.Amqp/Amqp/AmqpConstants.cs#L47
export const maxAbsoluteExpiryTime = new Date("9999-12-31T07:59:59.000Z").getTime();

Просмотреть файл

@ -23,6 +23,14 @@ export interface AsyncLockOptions {
Promise?: any;
}
export interface ServiceBusConnectionStringModel {
Endpoint: string;
SharedAccessKeyName: string;
SharedAccessKey: string;
EntityPath?: string;
[x: string]: any;
}
export interface EventHubConnectionStringModel {
Endpoint: string;
SharedAccessKeyName: string;
@ -167,3 +175,21 @@ export function isIotHubConnectionString(connectionString: string): boolean {
}
return result;
}
export function setIfDefined(obj: any, key: string, value: any): void {
if (value !== undefined) {
obj[key] = value;
}
}
export function verifyType(value: any, type: 'string' | 'number'): void {
if (value != undefined && typeof value !== type) {
throw new TypeError(`Invalid type provided. Value must be a ${type}.`);
}
}
export function verifyClass(value: any, clazz: Function, className: string): void {
if (value != undefined && !(value instanceof clazz)) {
throw new TypeError(`Invalid type provided. Value must be an instance of ${className}.`);
}
}

Просмотреть файл

@ -2,13 +2,12 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as debugModule from "debug";
import { ReceiverEvents, EventContext, OnAmqpEvent } from "./rhea-promise";
import { ReceiveOptions, EventData } from ".";
import { EventHubReceiver } from "./eventHubReceiver";
import { ConnectionContext } from "./connectionContext";
import { translate } from "./errors";
import * as rheaPromise from "./rhea-promise";
import * as Constants from "./util/constants";
import { Func } from "./util/utils";
import { translate, Func, Constants } from "./amqp-common";
const debug = debugModule("azure:event-hubs:receiverbatching");
@ -63,15 +62,15 @@ export class BatchingReceiver extends EventHubReceiver {
const eventDatas: EventData[] = [];
let timeOver = false;
return new Promise<EventData[]>((resolve, reject) => {
let onReceiveMessage: rheaPromise.OnAmqpEvent;
let onReceiveError: rheaPromise.OnAmqpEvent;
let onReceiveMessage: OnAmqpEvent;
let onReceiveError: OnAmqpEvent;
let waitTimer: any;
let actionAfterWaitTimeout: Func<void, void>;
// Final action to be performed after maxMessageCount is reached or the maxWaitTime is over.
const finalAction = (timeOver: boolean, data?: EventData) => {
// Resetting the mode. Now anyone can call start() or receive() again.
this._receiver.removeListener(Constants.receiverError, onReceiveError);
this._receiver.removeListener(Constants.message, onReceiveMessage);
this._receiver!.removeHandler(ReceiverEvents.receiverError, onReceiveError);
this._receiver!.removeHandler(ReceiverEvents.message, onReceiveMessage);
if (!data) {
data = eventDatas.length ? eventDatas[eventDatas.length - 1] : undefined;
}
@ -94,7 +93,7 @@ export class BatchingReceiver extends EventHubReceiver {
};
// Action to be performed on the "message" event.
onReceiveMessage = (context: rheaPromise.EventContext) => {
onReceiveMessage = (context: EventContext) => {
const data: EventData = EventData.fromAmqpMessage(context.message!);
data.body = this._context.dataTransformer.decode(context.message!.body);
if (eventDatas.length <= maxMessageCount) {
@ -106,9 +105,9 @@ export class BatchingReceiver extends EventHubReceiver {
};
// Action to be taken when an error is received.
onReceiveError = (context: rheaPromise.EventContext) => {
this._receiver.removeListener(Constants.receiverError, onReceiveError);
this._receiver.removeListener(Constants.message, onReceiveMessage);
onReceiveError = (context: EventContext) => {
this._receiver!.removeHandler(ReceiverEvents.receiverError, onReceiveError);
this._receiver!.removeHandler(ReceiverEvents.message, onReceiveMessage);
const error = translate(context.receiver!.error!);
debug("[%s] Receiver '%s' received an error:\n%O", this._context.connectionId, this.name, error);
if (waitTimer) {
@ -120,21 +119,21 @@ export class BatchingReceiver extends EventHubReceiver {
const addCreditAndSetTimer = (reuse?: boolean) => {
debug("[%s] Receiver '%s', adding credit for receiving %d messages.",
this._context.connectionId, this.name, maxMessageCount);
this._receiver.add_credit(maxMessageCount);
this._receiver!.addCredit(maxMessageCount);
let msg: string = "[%s] Setting the wait timer for %d seconds for receiver '%s'.";
if (reuse) msg += " Receiver link already present, hence reusing it.";
debug(msg, this._context.connectionId, maxWaitTimeInSeconds, this.name);
waitTimer = setTimeout(actionAfterWaitTimeout, (maxWaitTimeInSeconds as number) * 1000);
};
if (!this._isOpen()) {
if (!this.isOpen()) {
debug("[%s] Receiver '%s', setting the prefetch count to 0.", this._context.connectionId, this.name);
this.prefetchCount = 0;
this._init(onReceiveMessage, onReceiveError).then(() => addCreditAndSetTimer()).catch(reject);
} else {
addCreditAndSetTimer(true);
this._receiver.on(Constants.message, onReceiveMessage);
this._receiver.on(Constants.receiverError, onReceiveError);
this._receiver!.registerHandler(ReceiverEvents.message, onReceiveMessage);
this._receiver!.registerHandler(ReceiverEvents.receiverError, onReceiveError);
}
});
}

Просмотреть файл

@ -4,7 +4,7 @@
import * as debugModule from "debug";
import * as uuid from "uuid/v4";
import { ConnectionContext } from "./connectionContext";
import { defaultLock } from "./util/utils";
import { defaultLock } from "./amqp-common";
const debug = debugModule("azure:event-hubs:clientEntity");
export interface ClientEntityOptions {
@ -89,7 +89,7 @@ export class ClientEntity {
* Creates a new ClientEntity instance.
* @constructor
* @param {ConnectionContext} context The connection context.
* @param {string} [name] Name of the entity.
* @param {ClientEntityOptions} [options] Options that can be provided while creating the ClientEntity.
*/
constructor(context: ConnectionContext, options?: ClientEntityOptions) {
if (!options) options = {};
@ -128,6 +128,10 @@ export class ClientEntity {
await defaultLock.acquire(this._context.cbsSession!.cbsLock,
() => { return this._context.cbsSession!.init(); });
const tokenObject = await this._context.tokenProvider.getToken(this.audience);
if (!this._context.connection) {
this._context.connection = this._context.cbsSession!.connection;
this._context.connectionId = this._context.cbsSession!.connection!.id;
}
debug("[%s] %s: calling negotiateClaim for audience '%s'.",
this._context.connectionId, this.type, this.audience);
// Acquire the lock to negotiate the CBS claim.

Просмотреть файл

@ -3,16 +3,16 @@
import * as debugModule from "debug";
import * as uuid from "uuid/v4";
import * as Constants from "./util/constants";
import { ConnectionConfig } from ".";
import { packageJsonInfo } from "./util/constants";
import { EventHubReceiver } from "./eventHubReceiver";
import { EventHubSender } from "./eventHubSender";
import { TokenProvider } from "./auth/token";
import {
TokenProvider, CbsClient, DataTransformer, DefaultDataTransformer, SasTokenProvider,
Constants, ConnectionConfig
} from "./amqp-common";
import { ManagementClient, ManagementClientOptions } from "./managementClient";
import { CbsClient } from "./cbs";
import { SasTokenProvider } from "./auth/sas";
import { ClientOptions } from "./eventHubClient";
import { DataTransformer, DefaultDataTransformer } from "./dataTransformer";
import { Connection, Dictionary } from "./rhea-promise";
const debug = debugModule("azure:event-hubs:connectionContext");
@ -27,9 +27,9 @@ export interface ConnectionContext {
*/
readonly config: ConnectionConfig;
/**
* @property {any} [connection] The underlying AMQP connection.
* @property {Connection} [connection] The underlying AMQP connection.
*/
connection?: any;
connection?: Connection;
/**
* @property {string} [connectionId] The amqp connection id that uniquely identifies the connection within a process.
*/
@ -47,11 +47,11 @@ export interface ConnectionContext {
/**
* @property {Dictionary<EventHubReceiver>} receivers A dictionary of the EventHub Receivers associated with this client.
*/
receivers: { [x: string]: EventHubReceiver };
receivers: Dictionary<EventHubReceiver>;
/**
* @property {Dictionary<EventHubSender>} senders A dictionary of the EventHub Senders associated with this client.
*/
senders: { [x: string]: EventHubSender };
senders: Dictionary<EventHubSender>;
/**
* @property {ManagementClient} managementSession A reference to the management session ($management endpoint) on
* the underlying amqp connection for the EventHub Client.
@ -87,8 +87,8 @@ export namespace ConnectionContext {
export const userAgent: string = "/js-event-hubs";
export function create(config: ConnectionConfig, options?: ConnectionContextOptions): ConnectionContext {
ConnectionConfig.validate(config);
if (!options) options = {};
ConnectionConfig.validate(config, { isEntityPathRequired: true });
const context: ConnectionContext = {
connectionLock: `${Constants.establishConnection}-${uuid()}`,
negotiateClaimLock: `${Constants.negotiateClaim}-${uuid()}`,
@ -99,7 +99,8 @@ export namespace ConnectionContext {
receivers: {},
dataTransformer: options.dataTransformer || new DefaultDataTransformer()
};
context.cbsSession = new CbsClient(context);
const packageVersion = packageJsonInfo.version;
context.cbsSession = new CbsClient(config, packageVersion, userAgent);
const mOptions: ManagementClientOptions = {
address: options.managementSessionAddress,
audience: options.managementSessionAudience

Просмотреть файл

@ -1,11 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as Constants from "./util/constants";
import {
Message, MessageProperties, MessageHeader, EventHubMessageAnnotations, Dictionary
Message, MessageProperties, MessageHeader, Dictionary, messageHeader, messageProperties
} from "./rhea-promise";
import { EventHubMessageAnnotations, Constants } from "./amqp-common";
/**
* Describes the structure of an event to be sent or received from the EventHub.
@ -71,16 +70,6 @@ export interface EventData {
_raw_amqp_mesage?: Message;
}
export const messageProperties: string[] = [
"message_id", "reply_to", "to", "correlation_id", "content_type", "absolute_expiry_time",
"group_id", "group_sequence", "reply_to_group_id", "content_encoding", "creation_time", "subject",
"user_id"
];
export const messageHeader: string[] = [
"first_acquirer", "delivery_count", "ttl", "durable", "priority"
];
/**
* Describes the methods on the EventData interface.
* @module EventData

Просмотреть файл

@ -2,13 +2,17 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as debugModule from "debug";
import { closeConnection } from "./rhea-promise";
import { Delivery } from "rhea";
import { ApplicationTokenCredentials, DeviceTokenCredentials, UserTokenCredentials, MSITokenCredentials } from "ms-rest-azure";
import { ConnectionConfig, OnMessage, OnError, EventData, EventHubsError, DataTransformer } from ".";
import { Delivery } from "./rhea-promise";
import {
ApplicationTokenCredentials, DeviceTokenCredentials, UserTokenCredentials, MSITokenCredentials
} from "ms-rest-azure";
import {
ConnectionConfig, MessagingError, DataTransformer, TokenProvider,
AadTokenProvider
} from "./amqp-common";
import { OnMessage, OnError } from "./eventHubReceiver";
import { EventData } from "./eventData";
import { ConnectionContext } from "./connectionContext";
import { TokenProvider } from "./auth/token";
import { AadTokenProvider } from "./auth/aad";
import { EventHubPartitionRuntimeInformation, EventHubRuntimeInformation } from "./managementClient";
import { EventPosition } from "./eventPosition";
import { EventHubSender } from "./eventHubSender";
@ -113,7 +117,7 @@ export class EventHubClient {
*/
async close(): Promise<any> {
try {
if (this._context.connection) {
if (this._context.connection && this._context.connection.isOpen()) {
// Close all the senders.
for (const sender of Object.values(this._context.senders)) {
await sender.close();
@ -126,7 +130,7 @@ export class EventHubClient {
await this._context.cbsSession!.close();
// Close the management session
await this._context.managementSession!.close();
await closeConnection(this._context.connection);
await this._context.connection.close();
debug("Closed the amqp connection '%s' on the client.", this._context.connectionId);
this._context.connection = undefined;
}
@ -231,7 +235,7 @@ export class EventHubClient {
throw new Error("'partitionId' is a required parameter and must be of type: 'string' | 'number'.");
}
const bReceiver = BatchingReceiver.create(this._context, partitionId, options);
let error: EventHubsError | undefined;
let error: MessagingError | undefined;
let result: EventData[] = [];
try {
result = await bReceiver.receive(maxMessageCount, maxWaitTimeInSeconds);

Просмотреть файл

@ -3,10 +3,9 @@
import * as rhea from "rhea";
import * as debugModule from "debug";
import * as rheaPromise from "./rhea-promise";
import { translate } from "./errors";
import * as Constants from "./util/constants";
import { ReceiveOptions, EventData, EventHubsError } from ".";
import { Receiver, OnAmqpEvent, EventContext, ReceiverOptions } from "./rhea-promise";
import { translate, Constants, MessagingError } from "./amqp-common";
import { ReceiveOptions, EventData } from ".";
import { ConnectionContext } from "./connectionContext";
import { ClientEntity } from "./clientEntity";
@ -47,7 +46,7 @@ export type OnMessage = (eventData: EventData) => void;
/**
* Describes the error handler signature.
*/
export type OnError = (error: EventHubsError | Error) => void;
export type OnError = (error: MessagingError | Error) => void;
/**
* Describes the EventHubReceiver that will receive event data from EventHub.
@ -83,10 +82,10 @@ export class EventHubReceiver extends ClientEntity {
*/
receiverRuntimeMetricEnabled: boolean = false;
/**
* @property {any} [_receiver] The AMQP receiver link.
* @property {Receiver} [_receiver] The AMQP receiver link.
* @protected
*/
protected _receiver?: any;
protected _receiver?: Receiver;
/**
* @property {OnMessage} _onMessage The message handler provided by the user that will be wrapped
* inside _onAmqpMessage.
@ -104,13 +103,13 @@ export class EventHubReceiver extends ClientEntity {
* underlying rhea receiver for the "message" event.
* @protected
*/
protected _onAmqpMessage: rheaPromise.OnAmqpEvent;
protected _onAmqpMessage: OnAmqpEvent;
/**
* @property {OnMessage} _onMessage The message handler that will be set as the handler on the
* underlying rhea receiver for the "receiver_error" event.
* @protected
*/
protected _onAmqpError: rheaPromise.OnAmqpEvent;
protected _onAmqpError: OnAmqpEvent;
/**
* Instantiate a new receiver from the AMQP `Receiver`. Used by `EventHubClient`.
@ -118,18 +117,7 @@ export class EventHubReceiver extends ClientEntity {
* @constructor
* @param {EventHubClient} client The EventHub client.
* @param {string} partitionId Partition ID from which to receive.
* @param {ReceiveOptions} [options] Options for how you'd like to connect.
* @param {string} [options.consumerGroup] Consumer group from which to receive.
* @param {number} [options.prefetchCount] The upper limit of events this receiver will
* actively receive regardless of whether a receive operation is pending.
* @param {boolean} [options.enableReceiverRuntimeMetric] Provides the approximate receiver runtime information
* for a logical partition of an Event Hub if the value is true. Default false.
* @param {number} [options.epoch] The epoch value that this receiver is currently
* using for partition ownership. A value of undefined means this receiver is not an epoch-based receiver.
* @param {EventPosition} [options.eventPosition] The position of EventData in the EventHub parition from
* where the receiver should start receiving. Only one of offset, sequenceNumber, enqueuedTime, customFilter can be specified.
* `EventPosition.withCustomFilter()` should be used if you want more fine-grained control of the filtering.
* See https://github.com/Azure/amqpnetlite/wiki/Azure%20Service%20Bus%20Event%20Hubs for details.
* @param {ReceiveOptions} [options] Receiver options.
*/
constructor(context: ConnectionContext, partitionId: string | number, options?: ReceiveOptions) {
super(context, { partitionId: partitionId, name: options ? options.name : undefined });
@ -145,7 +133,7 @@ export class EventHubReceiver extends ClientEntity {
this.runtimeInfo = {
partitionId: `${partitionId}`
};
this._onAmqpMessage = (context: rheaPromise.EventContext) => {
this._onAmqpMessage = (context: EventContext) => {
const evData = EventData.fromAmqpMessage(context.message!);
evData.body = this._context.dataTransformer.decode(context.message!.body);
@ -158,7 +146,7 @@ export class EventHubReceiver extends ClientEntity {
this._onMessage!(evData);
};
this._onAmqpError = (context: rheaPromise.EventContext) => {
this._onAmqpError = (context: EventContext) => {
const ehError = translate(context.receiver!.error!);
// TODO: Should we retry before calling user's error method?
debug("[%s] An error occurred for Receiver '%s': %O.",
@ -167,7 +155,13 @@ export class EventHubReceiver extends ClientEntity {
};
}
/**
* Determines whether the AMQP receiver link is open. If open then returns true else returns false.
* @return {boolean} boolean
*/
isOpen(): boolean {
return this._receiver! && this._receiver!.isOpen();
}
/**
* Closes the underlying AMQP receiver.
@ -175,14 +169,10 @@ export class EventHubReceiver extends ClientEntity {
async close(): Promise<void> {
if (this._receiver) {
try {
// TODO: should I call _receiver.detach() or _receiver.close()?
// should I also call this._session.close() after closing the reciver
// or can I directly close the session which will take care of closing the receiver as well.
await rheaPromise.closeReceiver(this._receiver);
await this._receiver.close();
// Resetting the mode.
debug("[%s] Deleted the receiver '%s' from the client cache.", this._context.connectionId, this.name);
this._receiver = undefined;
this._session = undefined;
clearTimeout(this._tokenRenewalTimer as NodeJS.Timer);
debug("[%s] Receiver '%s', has been closed.", this._context.connectionId, this.name);
} catch (err) {
@ -195,9 +185,9 @@ export class EventHubReceiver extends ClientEntity {
* Creates a new AMQP receiver under a new AMQP session.
* @returns {Promise<void>}
*/
protected async _init(onAmqpMessage?: rheaPromise.OnAmqpEvent, onAmqpError?: rheaPromise.OnAmqpEvent): Promise<void> {
protected async _init(onAmqpMessage?: OnAmqpEvent, onAmqpError?: OnAmqpEvent): Promise<void> {
try {
if (!this._isOpen()) {
if (!this.isOpen()) {
await this._negotiateClaim();
if (!onAmqpMessage) {
onAmqpMessage = this._onAmqpMessage;
@ -205,10 +195,9 @@ export class EventHubReceiver extends ClientEntity {
if (!onAmqpError) {
onAmqpError = this._onAmqpError;
}
this._session = await rheaPromise.createSession(this._context.connection);
debug("[%s] Trying to create receiver '%s'...", this._context.connectionId, this.name);
const rcvrOptions = this._createReceiverOptions();
this._receiver = await rheaPromise.createReceiverWithHandlers(this._session, onAmqpMessage, onAmqpError, rcvrOptions);
const rcvrOptions = this._createReceiverOptions(onAmqpMessage, onAmqpError);
this._receiver = await this._context.connection!.createReceiver(rcvrOptions);
debug("Promise to create the receiver resolved. Created receiver with name: ", this.name);
debug("[%s] Receiver '%s' created with receiver options: %O",
this._context.connectionId, this.name, rcvrOptions);
@ -225,34 +214,20 @@ export class EventHubReceiver extends ClientEntity {
}
}
/**
* Determines whether the AMQP receiver link is open. If open then returns true else returns false.
* @protected
*
* @return {boolean} boolean
*/
protected _isOpen(): boolean {
let result: boolean = false;
if (this._session && this._receiver) {
if (this._receiver.is_open && this._receiver.is_open()) {
result = true;
}
}
return result;
}
/**
* Creates the options that need to be specified while creating an AMQP receiver link.
* @private
*/
private _createReceiverOptions(): rheaPromise.ReceiverOptions {
const rcvrOptions: rheaPromise.ReceiverOptions = {
private _createReceiverOptions(onMessage?: OnAmqpEvent, onError?: OnAmqpEvent): ReceiverOptions {
const rcvrOptions: ReceiverOptions = {
name: this.name,
autoaccept: true,
source: {
address: this.address
},
credit_window: this.prefetchCount,
onMessage: onMessage,
onError: onError
};
if (this.epoch !== undefined && this.epoch !== null) {
if (!rcvrOptions.properties) rcvrOptions.properties = {};

Просмотреть файл

@ -1,15 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as rhea from "rhea";
import * as debugModule from "debug";
import * as uuid from "uuid/v4";
import { translate } from "./errors";
import * as rheaPromise from "./rhea-promise";
import { EventData, messageProperties } from "./eventData";
import {
messageProperties, Sender, EventContext, OnAmqpEvent, SenderOptions, Delivery, SenderEvents,
message
} from "./rhea-promise";
import { EventData } from "./eventData";
import { ConnectionContext } from "./connectionContext";
import { defaultLock, Func } from "./util/utils";
import { retry } from "./retry";
import { defaultLock, Func, retry, translate, AmqpMessage } from "./amqp-common";
import { ClientEntity } from "./clientEntity";
const debug = debugModule("azure:event-hubs:sender");
@ -26,10 +26,10 @@ export class EventHubSender extends ClientEntity {
*/
readonly senderLock: string = `sender-${uuid()}`;
/**
* @property {any} [_sender] The AMQP sender link.
* @property {Sender} [_sender] The AMQP sender link.
* @private
*/
private _sender?: any;
private _sender?: Sender;
/**
* Creates a new EventHubSender instance.
@ -51,9 +51,9 @@ export class EventHubSender extends ClientEntity {
* Sends the given message, with the given options on this link
*
* @param {any} data Message to send. Will be sent as UTF8-encoded JSON string.
* @returns {Promise<rheaPromise.Delivery>} Promise<rheaPromise.Delivery>
* @returns {Promise<Delivery>} Promise<Delivery>
*/
async send(data: EventData): Promise<rheaPromise.Delivery> {
async send(data: EventData): Promise<Delivery> {
try {
if (!data || (data && typeof data !== "object")) {
throw new Error("data is required and it must be of type object.");
@ -78,9 +78,9 @@ export class EventHubSender extends ClientEntity {
* "application_properties" and "properties" of the first message will be set as that
* of the envelope (batch message).
* @param {Array<EventData>} datas An array of EventData objects to be sent in a Batch message.
* @return {Promise<rheaPromise.Delivery>} Promise<rheaPromise.Delivery>
* @return {Promise<Delivery>} Promise<Delivery>
*/
async sendBatch(datas: EventData[]): Promise<rheaPromise.Delivery> {
async sendBatch(datas: EventData[]): Promise<Delivery> {
try {
if (!datas || (datas && !Array.isArray(datas))) {
throw new Error("data is required and it must be an Array.");
@ -93,7 +93,7 @@ export class EventHubSender extends ClientEntity {
}
debug("[%s] Sender '%s', trying to send EventData[]: %O",
this._context.connectionId, this.name, datas);
const messages: rhea.Message[] = [];
const messages: AmqpMessage[] = [];
// Convert EventData to AmqpMessage.
for (let i = 0; i < datas.length; i++) {
const message = EventData.toAmqpMessage(datas[i]);
@ -101,8 +101,8 @@ export class EventHubSender extends ClientEntity {
messages[i] = message;
}
// Encode every amqp message and then convert every encoded message to amqp data section
const batchMessage: rhea.Message = {
body: rhea.message.data_sections(messages.map(rhea.message.encode))
const batchMessage: AmqpMessage = {
body: message.data_sections(messages.map(message.encode))
};
// Set message_annotations, application_properties and properties of the first message as
// that of the envelope (batch message).
@ -119,7 +119,7 @@ export class EventHubSender extends ClientEntity {
}
// Finally encode the envelope (batch message).
const encodedBatchMessage = rhea.message.encode(batchMessage);
const encodedBatchMessage = message.encode(batchMessage);
debug("[%s]Sender '%s', sending encoded batch message.",
this._context.connectionId, this.name, encodedBatchMessage);
return await this._trySend(encodedBatchMessage, undefined, 0x80013700);
@ -137,12 +137,11 @@ export class EventHubSender extends ClientEntity {
async close(): Promise<void> {
if (this._sender) {
try {
await rheaPromise.closeSender(this._sender);
await this._sender.close();
delete this._context.senders[this.name!];
debug("[%s] Deleted the sender '%s' with address '%s' from the client cache.",
this._context.connectionId, this.name, this.address);
this._sender = undefined;
this._session = undefined;
clearTimeout(this._tokenRenewalTimer as NodeJS.Timer);
debug("[%s]Sender '%s' closed.", this._context.connectionId, this.name);
} catch (err) {
@ -152,12 +151,13 @@ export class EventHubSender extends ClientEntity {
}
}
private _createSenderOptions(): rheaPromise.SenderOptions {
const options: rheaPromise.SenderOptions = {
private _createSenderOptions(onError?: OnAmqpEvent): SenderOptions {
const options: SenderOptions = {
name: this.name,
target: {
address: this.address
}
},
onError: onError
};
debug("Creating sender with options: %O", options);
return options;
@ -171,26 +171,26 @@ export class EventHubSender extends ClientEntity {
* for the message to be accepted or rejected and accordingly resolve or reject the promise.
*
* @param message The message to be sent to EventHub.
* @return {Promise<rheaPromise.Delivery>} Promise<rheaPromise.Delivery>
* @return {Promise<Delivery>} Promise<Delivery>
*/
private _trySend(message: rhea.Message, tag?: any, format?: number): Promise<rheaPromise.Delivery> {
const sendEventPromise = new Promise<rheaPromise.Delivery>((resolve, reject) => {
private _trySend(message: AmqpMessage, tag?: any, format?: number): Promise<Delivery> {
const sendEventPromise = new Promise<Delivery>((resolve, reject) => {
debug("[%s] Sender '%s', credit: %d available: %d", this._context.connectionId, this.name,
this._sender.credit, this._sender.session.outgoing.available());
if (this._sender.sendable()) {
this._sender!.credit, this._sender!.session.outgoing.available());
if (this._sender!.sendable()) {
debug("[%s] Sender '%s', sending message: %O", this._context.connectionId, this.name, message);
let onRejected: Func<rheaPromise.EventContext, void>;
let onReleased: Func<rheaPromise.EventContext, void>;
let onModified: Func<rheaPromise.EventContext, void>;
let onAccepted: Func<rheaPromise.EventContext, void>;
let onRejected: Func<EventContext, void>;
let onReleased: Func<EventContext, void>;
let onModified: Func<EventContext, void>;
let onAccepted: Func<EventContext, void>;
const removeListeners = (): void => {
this._sender.removeListener("rejected", onRejected);
this._sender.removeListener("accepted", onAccepted);
this._sender.removeListener("released", onReleased);
this._sender.removeListener("modified", onModified);
this._sender!.removeHandler(SenderEvents.rejected, onRejected);
this._sender!.removeHandler(SenderEvents.accepted, onAccepted);
this._sender!.removeHandler(SenderEvents.released, onReleased);
this._sender!.removeHandler(SenderEvents.modified, onModified);
};
onAccepted = (context: rheaPromise.EventContext) => {
onAccepted = (context: EventContext) => {
// Since we will be adding listener for accepted and rejected event every time
// we send a message, we need to remove listener for both the events.
// This will ensure duplicate listeners are not added for the same event.
@ -198,12 +198,12 @@ export class EventHubSender extends ClientEntity {
debug("[%s] Sender '%s', got event accepted.", this._context.connectionId, this.name);
resolve(context.delivery);
};
onRejected = (context: rheaPromise.EventContext) => {
onRejected = (context: EventContext) => {
removeListeners();
debug("[%s] Sender '%s', got event rejected.", this._context.connectionId, this.name);
reject(translate(context!.delivery!.remote_state!.error));
};
onReleased = (context: rheaPromise.EventContext) => {
onReleased = (context: EventContext) => {
removeListeners();
debug("[%s] Sender '%s', got event released.", this._context.connectionId, this.name);
let err: Error;
@ -215,7 +215,7 @@ export class EventHubSender extends ClientEntity {
}
reject(err);
};
onModified = (context: rheaPromise.EventContext) => {
onModified = (context: EventContext) => {
removeListeners();
debug("[%s] Sender '%s', got event modified.", this._context.connectionId, this.name);
let err: Error;
@ -227,11 +227,11 @@ export class EventHubSender extends ClientEntity {
}
reject(err);
};
this._sender.on("accepted", onAccepted);
this._sender.on("rejected", onRejected);
this._sender.on("modified", onModified);
this._sender.on("released", onReleased);
const delivery = this._sender.send(message, tag, format);
this._sender!.registerHandler(SenderEvents.accepted, onAccepted);
this._sender!.registerHandler(SenderEvents.rejected, onRejected);
this._sender!.registerHandler(SenderEvents.modified, onModified);
this._sender!.registerHandler(SenderEvents.released, onReleased);
const delivery = this._sender!.send(message, tag, format);
debug("[%s] Sender '%s', sent message with delivery id: %d",
this._context.connectionId, this.name, delivery.id);
} else {
@ -242,7 +242,7 @@ export class EventHubSender extends ClientEntity {
}
});
return retry<rheaPromise.Delivery>(() => sendEventPromise);
return retry<Delivery>(() => sendEventPromise);
}
/**
@ -252,13 +252,7 @@ export class EventHubSender extends ClientEntity {
* @return {boolean} boolean
*/
private _isOpen(): boolean {
let result: boolean = false;
if (this._session && this._sender) {
if (this._sender.is_open && this._sender.is_open()) {
result = true;
}
}
return result;
return this._sender! && this._sender!.isOpen();
}
/**
@ -269,16 +263,15 @@ export class EventHubSender extends ClientEntity {
try {
if (!this._isOpen()) {
await this._negotiateClaim();
const onAmqpError = (context: rheaPromise.EventContext) => {
const onAmqpError: OnAmqpEvent = (context: EventContext) => {
const senderError = translate(context.sender!.error!);
// TODO: Should we retry before calling user's error method?
debug("[%s] An error occurred for sender '%s': %O.",
this._context.connectionId, this.name, senderError);
};
this._session = await rheaPromise.createSession(this._context.connection);
debug("[%s] Trying to create sender '%s'...", this._context.connectionId, this.name);
const options = this._createSenderOptions();
this._sender = await rheaPromise.createSenderWithHandlers(this._session, onAmqpError, options);
const options = this._createSenderOptions(onAmqpError);
this._sender = await this._context.connection!.createSender(options);
debug("[%s] Promise to create the sender resolved. Created sender with name: %s",
this._context.connectionId, this.name);
debug("[%s] Sender '%s' created with sender options: %O",

Просмотреть файл

@ -1,9 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as Constants from "./util/constants";
import { translate } from "./errors";
import { ErrorNameConditionMapper } from ".";
import { translate, Constants, ErrorNameConditionMapper } from "./amqp-common";
/**
* Describes the options that can be set while creating an EventPosition.
@ -109,7 +107,10 @@ export class EventPosition {
}
if (!result) {
throw translate({ condition: ErrorNameConditionMapper.ArgumentError, description: "No starting position was set in the EventPosition." });
throw translate({
condition: ErrorNameConditionMapper.ArgumentError,
description: "No starting position was set in the EventPosition."
});
}
return result;
}

Просмотреть файл

@ -3,22 +3,21 @@
export { EventData } from "./eventData";
export {
Delivery, AmqpError, Message, MessageHeader, MessageProperties, Dictionary,
EventHubDeliveryAnnotations, EventHubMessageAnnotations
Delivery, AmqpError, Message, MessageHeader, MessageProperties, Dictionary
} from "./rhea-promise";
export { ConnectionConfig } from "./connectionConfig";
export { ReceiverRuntimeInfo, OnMessage, OnError } from "./eventHubReceiver";
export { ReceiveHandler } from "./streamingReceiver";
export {
EventHubsError, ErrorNameConditionMapper, ConditionStatusMapper, ConditionErrorNameMapper
} from "./errors";
export { EventHubClient, ReceiveOptions, ClientOptionsBase, ClientOptions } from "./eventHubClient";
export { EventPosition } from "./eventPosition";
export { DataTransformer, DefaultDataTransformer } from "./dataTransformer";
export { EventHubPartitionRuntimeInformation, EventHubRuntimeInformation } from "./managementClient";
export { TokenType, TokenProvider, TokenInfo } from "./auth/token";
export { aadEventHubsAudience } from "./util/constants";
export {
TokenType, TokenProvider, TokenInfo, EventHubDeliveryAnnotations, EventHubMessageAnnotations
} from "./amqp-common";
import { Constants } from "./amqp-common";
export const aadEventHubsAudience = Constants.aadEventHubsAudience;
export {
delay, Timeout, EventHubConnectionStringModel, parseConnectionString,
IotHubConnectionStringModel, StorageConnectionStringModel, isIotHubConnectionString
} from "./util/utils";
IotHubConnectionStringModel, StorageConnectionStringModel, isIotHubConnectionString,
ErrorNameConditionMapper, ConditionStatusMapper, ConditionErrorNameMapper, MessagingError,
DataTransformer, DefaultDataTransformer, ConnectionConfig
} from "./amqp-common";

Просмотреть файл

@ -1,8 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import { parseConnectionString, IotHubConnectionStringModel } from "../util/utils";
import { ConnectionConfig } from "../connectionConfig";
import { parseConnectionString, IotHubConnectionStringModel, ConnectionConfig } from "../amqp-common";
export interface IotHubConnectionConfig {
/**

Просмотреть файл

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import { SasTokenProvider } from "../auth/sas";
import { SasTokenProvider } from "../amqp-common";
import { TokenInfo } from "..";
/**

Просмотреть файл

@ -5,8 +5,8 @@ import { IotHubConnectionConfig } from "./iotHubConnectionConfig";
import { ConnectionContext, ConnectionContextOptions } from "../connectionContext";
import { IotSasTokenProvider } from "./iotSas";
import * as debugModule from "debug";
import { translate, EventHubsError } from "../errors";
import { closeConnection } from "../rhea-promise";
import { translate, MessagingError } from "../amqp-common";
import { } from "../rhea-promise";
const debug = debugModule("azure:event-hubs:iothubClient");
export interface ParsedRedirectError {
@ -78,7 +78,7 @@ export class IotHubClient {
// Close the management session
await context.managementSession!.close();
debug("IotHub management client closed.");
await closeConnection(context.connection);
await context.connection!.close();
debug("Closed the amqp connection '%s' on the iothub client.", context.connectionId);
context.connection = undefined;
}
@ -88,7 +88,7 @@ export class IotHubClient {
}
}
private _parseRedirectError(error: EventHubsError): ParsedRedirectError {
private _parseRedirectError(error: MessagingError): ParsedRedirectError {
if (!error) {
throw new Error("'error' is a required parameter and must be of type 'object'.");
}

Просмотреть файл

@ -2,15 +2,14 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as uuid from "uuid/v4";
import * as rheaPromise from "./rhea-promise";
import * as Constants from "./util/constants";
import * as debugModule from "debug";
import { RequestResponseLink, createRequestResponseLink, sendRequest } from "./rpc";
import { defaultLock } from "./util/utils";
import * as rheaPromise from "./rhea-promise";
import {
RequestResponseLink, defaultLock, translate, Constants
} from "./amqp-common";
import { Message } from ".";
import { ConnectionContext } from "./connectionContext";
import { ClientEntity } from "./clientEntity";
import { translate } from "./errors";
const debug = debugModule("azure:event-hubs:management");
@ -170,10 +169,9 @@ export class ManagementClient extends ClientEntity {
*/
async close(): Promise<void> {
try {
if (this._mgmtReqResLink) {
await rheaPromise.closeSession(this._mgmtReqResLink.session);
if (this._isMgmtRequestResponseLinkOpen()) {
await this._mgmtReqResLink!.close();
debug("Successfully closed the management session.");
this._session = undefined;
this._mgmtReqResLink = undefined;
clearTimeout(this._tokenRenewalTimer as NodeJS.Timer);
}
@ -185,7 +183,7 @@ export class ManagementClient extends ClientEntity {
}
private async _init(): Promise<void> {
if (!this._mgmtReqResLink) {
if (!this._isMgmtRequestResponseLinkOpen()) {
await this._negotiateClaim();
const rxopt: rheaPromise.ReceiverOptions = {
source: { address: this.address },
@ -194,7 +192,8 @@ export class ManagementClient extends ClientEntity {
};
const sropt: rheaPromise.SenderOptions = { target: { address: this.address } };
debug("Creating a session for $management endpoint");
this._mgmtReqResLink = await createRequestResponseLink(this._context.connection, sropt, rxopt);
this._mgmtReqResLink =
await RequestResponseLink.create(this._context.connection!, sropt, rxopt);
this._session = this._mgmtReqResLink.session;
debug("[%s] Created sender '%s' and receiver '%s' links for $management endpoint.",
this._context.connectionId, this._mgmtReqResLink.sender.name, this._mgmtReqResLink.receiver.name);
@ -227,12 +226,17 @@ export class ManagementClient extends ClientEntity {
if (partitionId && type === Constants.partition) {
request.application_properties!.partition = partitionId;
}
debug("[%s] Acquiring lock to get the management req res link.", this._context.connectionId);
await defaultLock.acquire(this.managementLock, () => { return this._init(); });
return sendRequest(this._context.connection, this._mgmtReqResLink!, request);
return await this._mgmtReqResLink!.sendRequest(request);
} catch (err) {
err = translate(err);
debug("An error occurred while making the request to $management endpoint: %O", err);
throw err;
}
}
private _isMgmtRequestResponseLinkOpen(): boolean {
return this._mgmtReqResLink! && this._mgmtReqResLink!.isOpen();
}
}

Просмотреть файл

@ -0,0 +1,246 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as rhea from "rhea";
import * as debugModule from "debug";
import { Session } from "./session";
import { Sender, SenderOptions } from "./sender";
import { Receiver, ReceiverOptions } from "./receiver";
import { Func, ConnectionEvents } from ".";
const debug = debugModule("rhea-promise:connection");
export interface SenderOptionsWithSession extends SenderOptions {
session?: Session;
}
export interface ReceiverOptionsWithSession extends ReceiverOptions {
session?: Session;
}
export interface ReqResLink {
sender: Sender;
receiver: Receiver;
session: Session;
}
export class Connection {
options?: rhea.ConnectionOptions;
private _connection: rhea.Connection;
constructor(options?: rhea.ConnectionOptions) {
this.options = options;
this._connection = rhea.connect(options);
}
get id(): string {
return this._connection.options.id!;
}
/**
* Creates a new amqp connection.
* @param {ConnectionOptions} [options] Options to be provided for establishing an amqp connection.
* @return {Promise<Connection>} Promise<Connection>
* - **Resolves** the promise with the Connection object when rhea emits the "connection_open" event.
* - **Rejects** the promise with an AmqpError when rhea emits the "connection_close" event while trying
* to establish an amqp connection.
*/
open(): Promise<Connection> {
return new Promise((resolve, reject) => {
if (!this.isOpen()) {
let onOpen: Func<rhea.EventContext, void>;
let onClose: Func<rhea.EventContext, void>;
let onTransportClose: Func<rhea.EventContext, void>;
const removeListeners: Function = () => {
this._connection!.removeListener("connection_open", onOpen);
this._connection!.removeListener("connection_close", onClose);
this._connection!.removeListener("disconnected", onClose);
this._connection!.removeListener("disconnected", onTransportClose);
};
onOpen = (context: rhea.EventContext) => {
removeListeners();
this._connection!.once("disconnected", onTransportClose);
process.nextTick(() => {
debug("Resolving the promise with amqp connection.");
resolve(this);
});
};
onClose = (context: rhea.EventContext) => {
removeListeners();
debug(`Error occurred while establishing amqp connection.`, context.connection.error);
reject(context.connection.error);
};
onTransportClose = (context: rhea.EventContext) => {
debug(`Error occurred on the amqp connection.`, context.connection.error);
this.close().then(() => {
context.connection = undefined as any;
}).catch((err: Error) => {
debug(`Error occurred while closing amqp connection.`, err);
});
};
this._connection!.once("connection_open", onOpen);
this._connection!.once("connection_close", onClose);
this._connection!.once("disconnected", onClose);
} else {
resolve(this);
}
});
}
/**
* Closes the amqp connection.
* @return {Promise<void>} Promise<void>
* - **Resolves** the promise when rhea emits the "connection_close" event.
* - **Rejects** the promise with an AmqpError when rhea emits the "connection_error" event while trying
* to close an amqp connection.
*/
close(): Promise<void> {
return new Promise<void>((resolve, reject) => {
if (this._connection && this._connection.is_open()) {
let onClose: Func<rhea.EventContext, void>;
let onError: Func<rhea.EventContext, void>;
onClose = (context: rhea.EventContext) => {
this._connection!.removeListener("connection_close", onClose);
process.nextTick(() => {
debug("Resolving the promise as the connection has been successfully closed.");
resolve();
});
};
onError = (context: rhea.EventContext) => {
this._connection!.removeListener("connection_error", onError);
debug(`Error occurred while closing amqp connection.`, context.connection.error);
reject(context.connection.error);
};
this._connection.once("connection_close", onClose);
this._connection.once("connection_error", onError);
this._connection.close();
} else {
resolve();
}
});
}
/**
* Determines whether the connection is open.
* @return {boolean} true if open false otherwise.
*/
isOpen(): boolean {
let result: boolean = false;
if (this._connection && this._connection.is_open && this._connection.is_open()) {
result = true;
}
return result;
}
/**
* Creates an amqp session on the provided amqp connection.
* @return {Promise<Session>} Promise<Session>
* - **Resolves** the promise with the Session object when rhea emits the "session_open" event.
* - **Rejects** the promise with an AmqpError when rhea emits the "session_close" event while trying
* to create an amqp session.
*/
createSession(): Promise<Session> {
return new Promise((resolve, reject) => {
const rheaSession = this._connection.create_session();
const session = new Session(this, rheaSession);
let onOpen: Func<rhea.EventContext, void>;
let onClose: Func<rhea.EventContext, void>;
const removeListeners = () => {
rheaSession.removeListener("session_open", onOpen);
rheaSession.removeListener("session_close", onClose);
};
onOpen = (context: rhea.EventContext) => {
removeListeners();
process.nextTick(() => {
debug("Resolving the promise with amqp session.");
resolve(session);
});
};
onClose = (context: rhea.EventContext) => {
removeListeners();
debug(`Error occurred while establishing a session over amqp connection.`, context.session.error);
reject(context.session.error);
};
rheaSession.once("session_open", onOpen);
rheaSession.once("session_close", onClose);
debug("Calling amqp session.begin().");
rheaSession.begin();
});
}
/**
* Creates an amqp sender link. It either uses the provided session or creates a new one.
* @param {SenderOptionsWithSession} options Optional parameters to create a sender link.
* @return {Promise<Sender>} Promise<Sender>.
*/
async createSender(options?: SenderOptionsWithSession): Promise<Sender> {
if (options && options.session) {
return await options.session.createSender(options);
}
const session = await this.createSession();
return await session.createSender(options);
}
/**
* Creates an amqp receiver link. It either uses the provided session or creates a new one.
* @param {ReceiverOptionsWithSession} options Optional parameters to create a receiver link.
* @return {Promise<Receiver>} Promise<Receiver>.
*/
async createReceiver(options?: ReceiverOptionsWithSession): Promise<Receiver> {
if (options && options.session) {
return await options.session.createReceiver(options);
}
const session = await this.createSession();
return await session.createReceiver(options);
}
/**
* Creates an amqp sender-receiver link. It either uses the provided session or creates a new one.
* This method creates a sender-receiver link on the same session. It is useful for management
* style operations where one may want to send a request and await for response.
* @param {SenderOptions} senderOptions Parameters to create a sender.
* @param {ReceiverOptions} receiverOptions Parameters to create a receiver.
* @param {Session} [session] The optional session on which the sender and receiver links will be
* created.
* @return {Promise<ReqResLink>} Promise<ReqResLink>
*/
async createRequestResponseLink(senderOptions: SenderOptions, receiverOptions: ReceiverOptions, providedSession?: Session): Promise<ReqResLink> {
if (!senderOptions) {
throw new Error(`Please provide sender options.`);
}
if (!receiverOptions) {
throw new Error(`Please provide receiver options.`);
}
const session = providedSession || await this.createSession();
const sender = await session.createSender(senderOptions);
const receiver = await session.createReceiver(receiverOptions);
debug("[%s] Successfully created the sender and receiver links on the same session.", this.id);
return {
session: session,
sender: sender,
receiver: receiver
};
}
registerHandler(event: ConnectionEvents, handler: rhea.OnAmqpEvent): void {
this._connection.on(event, handler);
}
removeHandler(event: ConnectionEvents, handler: rhea.OnAmqpEvent): void {
this._connection.removeListener(event, handler);
}
}

Просмотреть файл

@ -1,443 +1,22 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as rhea from "rhea";
import * as debugModule from "debug";
const debug = debugModule("rhea-promise");
export {
Delivery, Message, OnAmqpEvent, MessageProperties, MessageHeader, EventContext,
Connection, ReceiverOptions, SenderOptions, ConnectionOptions, AmqpError, Dictionary
ConnectionOptions, AmqpError, Dictionary, types, message, filter, Filter, MessageUtil,
uuid_to_string, generate_uuid, string_to_uuid, LinkError, ProtocolError, LinkOptions,
DeliveryAnnotations, MessageAnnotations, ReceiverEvents, SenderEvents, ConnectionEvents,
SessionEvents
} from "rhea";
/**
* Establishes an amqp connection.
* @param {ConnectionOptions} [options] Options to be provided for establishing an amqp connection.
* @return {Promise<Connection>} Promise<Connection>
* - **Resolves** the promise with the Connection object when rhea emits the "connection_open" event.
* - **Rejects** the promise with an AmqpError when rhea emits the "connection_close" event while trying
* to establish an amqp connection.
*/
export function connect(options?: rhea.ConnectionOptions): Promise<rhea.Connection> {
return new Promise((resolve, reject) => {
const connection = rhea.connect(options);
function removeListeners(connection: rhea.Connection): void {
connection.removeListener("connection_open", onOpen);
connection.removeListener("connection_close", onClose);
connection.removeListener("disconnected", onClose);
connection.removeListener("disconnected", onTransportClose);
}
function onOpen(context: rhea.EventContext): void {
removeListeners(connection);
connection.once("disconnected", onTransportClose);
process.nextTick(() => {
debug("Resolving the promise with amqp connection.");
resolve(connection);
});
}
function onClose(context: rhea.EventContext): void {
removeListeners(connection);
debug(`Error occurred while establishing amqp connection.`, context.connection.error);
reject(context.connection.error);
}
function onTransportClose(context: rhea.EventContext): void {
debug(`Error occurred on the amqp connection.`, context.connection.error);
closeConnection(context.connection).then(() => {
context.connection = undefined as any;
}).catch((err: Error) => {
debug(`Error occurred while closing amqp connection.`, err);
});
}
connection.once("connection_open", onOpen);
connection.once("connection_close", onClose);
connection.once("disconnected", onClose);
});
}
export { Connection, ReqResLink } from "./connection";
export { Session } from "./session";
export { Receiver, ReceiverOptions } from "./receiver";
export { Sender, SenderOptions } from "./sender";
/**
* Closes the amqp connection.
* @param {Connection} connection The amqp connection that needs to be closed.
* @return {Promise<void>} Promise<void>
* - **Resolves** the promise when rhea emits the "connection_close" event.
* - **Rejects** the promise with an AmqpError when rhea emits the "connection_error" event while trying
* to close an amqp connection.
*/
export function closeConnection(connection: rhea.Connection): Promise<void> {
if (!connection || (connection && typeof connection !== "object")) {
throw new Error("connection is a required parameter and must be of type 'object'.");
}
return new Promise<void>((resolve, reject) => {
if (connection.is_open()) {
function onClose(context: rhea.EventContext): void {
connection.removeListener("connection_close", onClose);
process.nextTick(() => {
debug("Resolving the promise as the connection has been successfully closed.");
resolve();
});
}
function onError(context: rhea.EventContext): void {
connection.removeListener("connection_error", onError);
debug(`Error occurred while closing amqp connection.`, context.connection.error);
reject(context.connection.error);
}
connection.once("connection_close", onClose);
connection.once("connection_error", onError);
connection.close();
} else {
resolve();
}
});
}
/**
* Creates an amqp session on the provided amqp connection.
* @param {Connection} connection The amqp connection object
* @return {Promise<Session>} Promise<Session>
* - **Resolves** the promise with the Session object when rhea emits the "session_open" event.
* - **Rejects** the promise with an AmqpError when rhea emits the "session_close" event while trying
* to create an amqp session.
*/
export function createSession(connection: rhea.Connection): Promise<rhea.Session> {
if (!connection || (connection && typeof connection !== "object")) {
throw new Error("connection is a required parameter and must be of type 'object'.");
}
return new Promise((resolve, reject) => {
const session = connection.create_session();
function removeListeners(session: rhea.Session): void {
session.removeListener("session_open", onOpen);
session.removeListener("session_close", onClose);
}
function onOpen(context: rhea.EventContext): void {
removeListeners(session);
process.nextTick(() => {
debug("Resolving the promise with amqp session.");
resolve(session);
});
}
function onClose(context: rhea.EventContext): void {
removeListeners(session);
debug(`Error occurred while establishing a session over amqp connection.`, context.session.error);
reject(context.session.error);
}
session.once("session_open", onOpen);
session.once("session_close", onClose);
debug("Calling amqp session.begin().");
session.begin();
});
}
/**
* Closes the amqp session.
* @param {Session} session The amqp session that needs to be closed.
* @return {Promise<void>} Promise<void>
* - **Resolves** the promise when rhea emits the "session_close" event.
* - **Rejects** the promise with an AmqpError when rhea emits the "session_error" event while trying
* to close an amqp session.
*/
export function closeSession(session: rhea.Session): Promise<void> {
if (!session || (session && typeof session !== "object")) {
throw new Error("session is a required parameter and must be of type 'object'.");
}
return new Promise<void>((resolve, reject) => {
if (session.is_open()) {
function onClose(context: rhea.EventContext): void {
session.removeListener("session_close", onClose);
process.nextTick(() => {
debug("Resolving the promise as the amqp session has been closed.");
resolve();
});
}
function onError(context: rhea.EventContext): void {
session.removeListener("session_error", onError);
debug(`Error occurred while closing amqp session.`, context.session.error);
reject(context.session.error);
}
session.once("session_close", onClose);
session.once("session_error", onError);
session.close();
} else {
resolve();
}
});
}
/**
* Creates an amqp sender on the provided amqp session.
* @param {Session} session The amqp session object on which the sender link needs to be established.
* @param {SenderOptions} [options] Options that can be provided while creating an amqp sender.
* @return {Promise<Sender>} Promise<Sender>
* - **Resolves** the promise with the Sender object when rhea emits the "sender_open" event.
* - **Rejects** the promise with an AmqpError when rhea emits the "sender_close" event while trying
* to create an amqp sender.
*/
export function createSender(session: rhea.Session, options?: rhea.SenderOptions): Promise<rhea.Sender> {
if (!session || (session && typeof session !== "object")) {
throw new Error("session is a required parameter and must be of type 'object'.");
}
return new Promise((resolve, reject) => {
const sender = session.attach_sender(options);
function removeListeners(session: rhea.Session): void {
sender.removeListener("sendable", onOpen);
sender.removeListener("sender_close", onClose);
}
function onOpen(context: rhea.EventContext): void {
removeListeners(session);
process.nextTick(() => {
debug(`Resolving the promise with amqp sender "${sender.name}".`);
resolve(sender);
});
}
function onClose(context: rhea.EventContext): void {
removeListeners(session);
debug(`Error occurred while creating a sender over amqp connection.`, context.sender!.error);
reject(context.sender!.error);
}
sender.once("sendable", onOpen);
sender.once("sender_close", onClose);
});
}
/**
* Creates an amqp sender on the provided amqp session.
* @param {Session} session The amqp session object on which the sender link needs to be established.
* @param {OnAmqpEvent} onError The event handler for the "error" event for the sender.
* @param {SenderOptions} [options] Options that can be provided while creating an amqp sender.
* @return {Promise<Sender>} Promise<Sender>
* - **Resolves** the promise with the Sender object when rhea emits the "sender_open" event.
* - **Rejects** the promise with an AmqpError when rhea emits the "sender_close" event while trying
* to create an amqp sender.
*/
export function createSenderWithHandlers(session: rhea.Session, onError: rhea.OnAmqpEvent, options?: rhea.SenderOptions): Promise<rhea.Sender> {
if (!session || (session && typeof session !== "object")) {
throw new Error("session is a required parameter and must be of type 'object'.");
}
return new Promise((resolve, reject) => {
const sender = session.attach_sender(options);
sender.on("sender_error", onError);
function removeListeners(session: rhea.Session): void {
sender.removeListener("sendable", onOpen);
sender.removeListener("sender_close", onClose);
}
function onOpen(context: rhea.EventContext): void {
removeListeners(session);
process.nextTick(() => {
debug(`Resolving the promise with amqp sender "${sender.name}".`);
resolve(sender);
});
}
function onClose(context: rhea.EventContext): void {
removeListeners(session);
debug(`Error occurred while creating a sender over amqp connection.`, context.sender!.error);
reject(context.sender!.error);
}
sender.once("sendable", onOpen);
sender.once("sender_close", onClose);
});
}
/**
* Closes the amqp sender.
* @param {Sender} sender The amqp sender that needs to be closed.
* @return {Promise<void>} Promise<void>
* - **Resolves** the promise when rhea emits the "sender_close" event.
* - **Rejects** the promise with an AmqpError when rhea emits the
* "sender_error" event while trying to close an amqp sender.
*/
export function closeSender(sender: rhea.Sender): Promise<void> {
if (!sender || (sender && typeof sender !== "object")) {
throw new Error("sender is a required parameter and must be of type 'object'.");
}
return new Promise<void>((resolve, reject) => {
if (sender.is_open()) {
function onClose(context: rhea.EventContext): void {
sender.removeListener("sender_close", onClose);
process.nextTick(() => {
debug("Resolving the promise as the amqp sender has been closed.");
resolve();
});
}
function onError(context: rhea.EventContext): void {
sender.removeListener("sender_error", onError);
debug(`Error occurred while closing amqp sender.`, context.sender!.error);
reject(context.sender!.error);
}
sender.once("sender_close", onClose);
sender.once("sender_error", onError);
sender.close();
} else {
resolve();
}
});
}
/**
* Creates an amqp receiver on the provided amqp session. This method should be used when you will be
* sending a request and waiting for a response from the service. For example: This method is useful
* while creating request/response links for $management or $cbs endpoint.
* @param {Session} session The amqp session object on which the receiver link needs to be established.
* @param {ReceiverOptions} [options] Options that can be provided while creating an amqp receiver.
* @return {Promise<Receiver>} Promise<Receiver>
* - **Resolves** the promise with the Receiver object when rhea emits the "receiver_open" event.
* - **Rejects** the promise with an AmqpError when rhea emits the "receiver_close" event while trying
* to create an amqp receiver.
*/
export function createReceiver(session: rhea.Session, options?: rhea.ReceiverOptions): Promise<rhea.Receiver> {
if (!session || (session && typeof session !== "object")) {
throw new Error("session is a required parameter and must be of type 'object'.");
}
return new Promise((resolve, reject) => {
const receiver = session.attach_receiver(options);
function removeListeners(receiver: rhea.Receiver): void {
receiver.removeListener("receiver_open", onOpen);
receiver.removeListener("receiver_close", onClose);
}
function onOpen(context: rhea.EventContext): void {
removeListeners(receiver);
process.nextTick(() => {
debug(`Resolving the promise with amqp receiver "${receiver.name}".`);
resolve(receiver);
});
}
function onClose(context: rhea.EventContext): void {
removeListeners(receiver);
debug(`Error occurred while creating a receiver over amqp connection.`, context.receiver!.error);
reject(context.receiver!.error);
}
receiver.once("receiver_open", onOpen);
receiver.once("receiver_close", onClose);
});
}
/**
* Creates an amqp receiver with provided message and error event handlers on the provided amqp session.
* This method should be used when you want to ensure that no messages are lost. For example: This method
* is useful for creating EventHub Receivers where you want to start receiving ASAP.
* @param {Session} session The amqp session object on which the receiver link needs to be established.
* @param {OnAmqpEvent} onMessage The event handler for the "message" event for the receiver.
* @param {OnAmqpEvent} onError The event handler for the "error" event for the receiver.
* @param {ReceiverOptions} [options] Options that can be provided while creating an amqp receiver.
* @return {Promise<Receiver>} Promise<Receiver>
* - **Resolves** the promise with the Receiver object when rhea emits the "receiver_open" event.
* - **Rejects** the promise with an AmqpError when rhea emits the "receiver_close" event while trying
* to create an amqp receiver.
*/
export function createReceiverWithHandlers(session: rhea.Session, onMessage: rhea.OnAmqpEvent, onError: rhea.OnAmqpEvent, options?: rhea.ReceiverOptions): Promise<rhea.Receiver> {
if (!session || (session && typeof session !== "object")) {
throw new Error("session is a required parameter and must be of type 'object'.");
}
if (!onMessage || (onMessage && typeof onMessage !== "function")) {
throw new Error("onMessage is a required parameter and must be of type 'function'.");
}
if (!onError || (onError && typeof onError !== "function")) {
throw new Error("onError is a required parameter and must be of type 'function'.");
}
return new Promise((resolve, reject) => {
const receiver = session.attach_receiver(options);
receiver.on("message", onMessage);
receiver.on("receiver_error", onError);
function removeListeners(receiver: any): void {
receiver.removeListener("receiver_open", onOpen);
receiver.removeListener("receiver_close", onClose);
}
function onOpen(context: rhea.EventContext): void {
removeListeners(receiver);
process.nextTick(() => {
debug(`Resolving the promise with amqp receiver "${receiver.name}".`);
resolve(receiver);
});
}
function onClose(context: rhea.EventContext): void {
removeListeners(receiver);
debug(`Error occurred while creating a receiver over amqp connection.`, context.receiver!.error);
reject(context.receiver!.error);
}
receiver.once("receiver_open", onOpen);
receiver.once("receiver_close", onClose);
});
}
/**
* Closes the amqp receiver.
* @param {Receiver} receiver The amqp receiver that needs to be closed.
* @return {Promise<void>} Promise<void>
* - **Resolves** the promise when rhea emits the "receiver_close" event.
* - **Rejects** the promise with an AmqpError when rhea emits the
* "receiver_error" event while trying to close an amqp receiver.
*/
export function closeReceiver(receiver: rhea.Receiver): Promise<void> {
if (!receiver || (receiver && typeof receiver !== "object")) {
throw new Error("receiver is a required parameter and must be of type 'object'.");
}
return new Promise<void>((resolve, reject) => {
if (receiver.is_open()) {
function onClose(context: rhea.EventContext): void {
receiver.removeListener("receiver_close", onClose);
process.nextTick(() => {
debug("Resolving the promise as the amqp receiver has been closed.");
resolve();
});
}
function onError(context: rhea.EventContext): void {
receiver.removeListener("receiver_error", onError);
debug(`Error occurred while closing amqp receiver.`, context.receiver!.error);
reject(context.receiver!.error);
}
receiver.once("receiver_close", onClose);
receiver.once("receiver_error", onError);
receiver.close();
} else {
resolve();
}
});
}
/**
* Defines a mapping for Http like response status codes for different status-code values provided by an AMQP broker.
* Defines a mapping for Http like response status codes for different status-code values
* provided by an AMQP broker.
* @enum AmqpResponseStatusCode
*/
export enum AmqpResponseStatusCode {
@ -490,55 +69,34 @@ export enum AmqpResponseStatusCode {
HttpVersionNotSupported = 505
}
/**
* Describes the delivery annotations.
* @interface
*/
export interface EventHubDeliveryAnnotations extends rhea.DeliveryAnnotations {
/**
* @property {string} [last_enqueued_offset] The offset of the last event.
*/
last_enqueued_offset?: string;
/**
* @property {number} [last_enqueued_sequence_number] The sequence number of the last event.
*/
last_enqueued_sequence_number?: number;
/**
* @property {number} [last_enqueued_time_utc] The enqueued time of the last event.
*/
last_enqueued_time_utc?: number;
/**
* @property {number} [runtime_info_retrieval_time_utc] The retrieval time of the last event.
*/
runtime_info_retrieval_time_utc?: number;
/**
* @property {string} Any unknown delivery annotations.
*/
[x: string]: any;
}
export const messageProperties: string[] = [
"message_id", "reply_to", "to", "correlation_id", "content_type", "absolute_expiry_time",
"group_id", "group_sequence", "reply_to_group_id", "content_encoding", "creation_time", "subject",
"user_id"
];
export const messageHeader: string[] = [
"first_acquirer", "delivery_count", "ttl", "durable", "priority"
];
/**
* Map containing message attributes that will be held in the message header.
* Type declaration for a Function type where T is the input to the function and V is the output of the function.
*/
export interface EventHubMessageAnnotations extends rhea.MessageAnnotations {
/**
* @property {string | null} [x-opt-partition-key] Annotation for the partition key set for the event.
*/
"x-opt-partition-key"?: string | null;
/**
* @property {number} [x-opt-sequence-number] Annontation for the sequence number of the event.
*/
"x-opt-sequence-number"?: number;
/**
* @property {number} [x-opt-enqueued-time] Annotation for the enqueued time of the event.
*/
"x-opt-enqueued-time"?: number;
/**
* @property {string} [x-opt-offset] Annotation for the offset of the event.
*/
"x-opt-offset"?: string;
/**
* @property {any} Any other annotation that can be added to the message.
*/
[x: string]: any;
export type Func<T, V> = (a: T) => V;
/**
* Determines whether the given error object is like an AmqpError object.
* @param err The AmqpError object
*/
export function isAmqpError(err: any): boolean {
if (!err || typeof err !== "object") {
throw new Error("err is a required parameter and must be of type 'object'.");
}
let result: boolean = false;
if (((err.condition && typeof err.condition === "string") && (err.description && typeof err.description === "string"))
|| (err.value && Array.isArray(err.value))
|| (err.constructor && err.constructor.name === "c")) {
result = true;
}
return result;
}

Просмотреть файл

@ -0,0 +1,136 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as rhea from "rhea";
import * as debugModule from "debug";
import { Session } from "./session";
import { Connection } from "./connection";
import { Func, ReceiverEvents } from ".";
const debug = debugModule("rhea-promise:receiver");
export interface ReceiverOptions extends rhea.ReceiverOptions {
onMessage?: rhea.OnAmqpEvent;
onError?: rhea.OnAmqpEvent;
}
export class Receiver {
receiverOptions?: ReceiverOptions;
private _session: Session;
private _receiver: rhea.Receiver;
constructor(session: Session, receiver: rhea.Receiver, options?: ReceiverOptions) {
this._session = session;
this._receiver = receiver;
this.receiverOptions = options;
}
get name(): string {
return this._receiver.name;
}
get error(): rhea.AmqpError | Error | undefined {
return this._receiver.error;
}
get properties(): rhea.Dictionary<any> {
return this._receiver.properties;
}
get source(): rhea.Source {
return this._receiver.source;
}
get target(): rhea.TerminusOptions {
return this._receiver.target;
}
get address(): string {
return this.source.address;
}
get session(): Session {
return this._session;
}
get connection(): Connection {
return this._session.connection;
}
get drain(): boolean {
return this._receiver.drain;
}
addCredit(credit: number): void {
this._receiver.add_credit(credit);
}
setCreditWindow(creditWindow: number): void {
this._receiver.set_credit_window(creditWindow);
}
/**
* Determines whether the sender link is open.
* @returns {boolean} `true` open. `false` closed.
*/
isOpen(): boolean {
let result = false;
if (this._session.isOpen() && this._receiver.is_open()) {
result = true;
}
return result;
}
remove(): void {
if (this._receiver) {
this._receiver.remove();
}
if (this._session) {
this._session.remove();
}
}
/**
* Closes the amqp receiver.
* @return {Promise<void>} Promise<void>
* - **Resolves** the promise when rhea emits the "receiver_close" event.
* - **Rejects** the promise with an AmqpError when rhea emits the
* "receiver_error" event while trying to close an amqp receiver.
*/
close(): Promise<void> {
const receiverClose = new Promise<void>((resolve, reject) => {
if (this.isOpen()) {
let onError: Func<rhea.EventContext, void>;
let onClose: Func<rhea.EventContext, void>;
onClose = (context: rhea.EventContext) => {
this._receiver.removeListener(ReceiverEvents.receiverClose, onClose);
process.nextTick(() => {
debug("Resolving the promise as the amqp receiver has been closed.");
resolve();
});
};
onError = (context: rhea.EventContext) => {
this._receiver.removeListener(ReceiverEvents.receiverError, onError);
debug(`Error occurred while closing amqp receiver.`, context.session.error);
reject(context.session.error);
};
this._receiver.once(ReceiverEvents.receiverClose, onClose);
this._receiver.once(ReceiverEvents.receiverError, onError);
this._receiver.close();
} else {
resolve();
}
});
return receiverClose.then(() => { return this._session.close(); });
}
registerHandler(event: ReceiverEvents, handler: rhea.OnAmqpEvent): void {
this._receiver.on(event, handler);
}
removeHandler(event: ReceiverEvents, handler: rhea.OnAmqpEvent): void {
this._receiver.removeListener(event, handler);
}
}

Просмотреть файл

@ -0,0 +1,143 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as rhea from "rhea";
import * as debugModule from "debug";
import { Session } from "./session";
import { Connection } from "./connection";
import { Func, SenderEvents } from ".";
const debug = debugModule("rhea-promise:sender");
export interface SenderOptions extends rhea.SenderOptions {
onAccepted?: rhea.OnAmqpEvent;
onRejected?: rhea.OnAmqpEvent;
onReleased?: rhea.OnAmqpEvent;
onModified?: rhea.OnAmqpEvent;
onError?: rhea.OnAmqpEvent;
}
export class Sender {
senderOptions?: SenderOptions;
private _session: Session;
private _sender: rhea.Sender;
constructor(session: Session, sender: rhea.Sender, options?: SenderOptions) {
this._session = session;
this._sender = sender;
this.senderOptions = options;
}
get name(): string {
return this._sender.name;
}
get error(): rhea.AmqpError | Error | undefined {
return this._sender.error;
}
get properties(): rhea.Dictionary<any> {
return this._sender.properties;
}
get source(): rhea.Source {
return this._sender.source;
}
get target(): rhea.TerminusOptions {
return this._sender.target;
}
get address(): string {
return this.source.address;
}
get credit(): number {
return (this._sender as any).credit;
}
get session(): Session {
return this._session;
}
get connection(): Connection {
return this._session.connection;
}
/**
* Determines whether the message is sendable.
* @returns {boolean} `true` Sendable. `false` Not Sendable.
*/
sendable(): boolean {
return this._sender.sendable();
}
send(msg: rhea.Message | Buffer, tag?: Buffer | string, format?: number): rhea.Delivery {
return this._sender.send(msg, tag, format);
}
/**
* Determines whether the sender link is open.
* @returns {boolean} `true` open. `false` closed.
*/
isOpen(): boolean {
let result = false;
if (this._session.isOpen() && this._sender.is_open()) {
result = true;
}
return result;
}
remove(): void {
if (this._sender) {
this._sender.remove();
}
if (this._session) {
this._session.remove();
}
}
/**
* Closes the amqp sender.
* @return {Promise<void>} Promise<void>
* - **Resolves** the promise when rhea emits the "sender_close" event.
* - **Rejects** the promise with an AmqpError when rhea emits the
* "sender_error" event while trying to close an amqp sender.
*/
close(): Promise<void> {
const senderClose = new Promise<void>((resolve, reject) => {
if (this.isOpen()) {
let onError: Func<rhea.EventContext, void>;
let onClose: Func<rhea.EventContext, void>;
onClose = (context: rhea.EventContext) => {
this._sender.removeListener(SenderEvents.senderClose, onClose);
process.nextTick(() => {
debug("Resolving the promise as the amqp sender has been closed.");
resolve();
});
};
onError = (context: rhea.EventContext) => {
this._sender.removeListener(SenderEvents.senderError, onError);
debug(`Error occurred while closing amqp sender.`, context.session.error);
reject(context.session.error);
};
this._sender.once(SenderEvents.senderClose, onClose);
this._sender.once(SenderEvents.senderError, onError);
this._sender.close();
} else {
resolve();
}
});
return senderClose.then(() => { return this._session.close(); });
}
registerHandler(event: SenderEvents, handler: rhea.OnAmqpEvent): void {
this._sender.on(event, handler);
}
removeHandler(event: SenderEvents, handler: rhea.OnAmqpEvent): void {
this._sender.removeListener(event, handler);
}
}

Просмотреть файл

@ -0,0 +1,200 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as rhea from "rhea";
import * as debugModule from "debug";
import { Connection } from "./connection";
import { Receiver, ReceiverOptions } from "./receiver";
import { Sender, SenderOptions } from "./sender";
import { Func, SenderEvents, ReceiverEvents } from ".";
const debug = debugModule("rhea-promise:session");
export class Session {
private _session: rhea.Session;
private _connection: Connection;
constructor(connection: Connection, session: rhea.Session) {
this._connection = connection;
this._session = session;
}
get connection(): Connection {
return this._connection;
}
get outgoing(): any {
return (this._session as any).outgoing;
}
isOpen(): boolean {
let result = false;
if (this._connection.isOpen() && this._session.is_open()) {
result = true;
}
return result;
}
remove(): void {
if (this._session) {
this._session.remove();
}
}
begin(): void {
if (this._session) {
this._session.begin();
}
}
/**
* Closes the amqp session.
* @return {Promise<void>} Promise<void>
* - **Resolves** the promise when rhea emits the "session_close" event.
* - **Rejects** the promise with an AmqpError when rhea emits the "session_error" event while trying
* to close an amqp session.
*/
close(): Promise<void> {
return new Promise<void>((resolve, reject) => {
if (this.isOpen()) {
const onClose = (context: rhea.EventContext) => {
this._session.removeListener("session_close", onClose);
process.nextTick(() => {
debug("Resolving the promise as the amqp session has been closed.");
resolve();
});
};
const onError = (context: rhea.EventContext) => {
this._session.removeListener("session_error", onError);
debug(`Error occurred while closing amqp session.`, context.session.error);
reject(context.session.error);
};
this._session.once("session_close", onClose);
this._session.once("session_error", onError);
this._session.close();
} else {
resolve();
}
});
}
/**
* Creates an amqp receiver on this session.
* @param {Session} session The amqp session object on which the receiver link needs to be established.
* @param {ReceiverOptions} [options] Options that can be provided while creating an amqp receiver.
* @return {Promise<Receiver>} Promise<Receiver>
* - **Resolves** the promise with the Receiver object when rhea emits the "receiver_open" event.
* - **Rejects** the promise with an AmqpError when rhea emits the "receiver_close" event while trying
* to create an amqp receiver.
*/
createReceiver(options?: ReceiverOptions): Promise<Receiver> {
if (options &&
((options.onMessage && !options.onError) || (options.onError && !options.onMessage))) {
throw new Error("Both onMessage and onError handlers must be provided if one of " +
"them is provided.");
}
const handlersProvided = options && options.onMessage ? true : false;
return new Promise((resolve, reject) => {
const rheaReceiver = this._session.attach_receiver(options);
const receiver = new Receiver(this, rheaReceiver, options);
let onOpen: Func<rhea.EventContext, void>;
let onClose: Func<rhea.EventContext, void>;
if (handlersProvided) {
rheaReceiver.on(ReceiverEvents.message, options!.onMessage!);
rheaReceiver.on(ReceiverEvents.receiverError, options!.onError!);
}
const removeListeners = () => {
rheaReceiver.removeListener("receiver_open", onOpen);
rheaReceiver.removeListener("receiver_close", onClose);
};
onOpen = (context: rhea.EventContext) => {
removeListeners();
process.nextTick(() => {
debug(`Resolving the promise with amqp receiver "${rheaReceiver.name}".`);
resolve(receiver);
});
};
onClose = (context: rhea.EventContext) => {
removeListeners();
debug(`Error occurred while creating a receiver over amqp connection.`, context.receiver!.error);
reject(context.receiver!.error);
};
rheaReceiver.once("receiver_open", onOpen);
rheaReceiver.once("receiver_close", onClose);
});
}
/**
* Creates an amqp sender on this session.
* @param {SenderOptions} [options] Options that can be provided while creating an amqp sender.
* @return {Promise<Sender>} Promise<Sender>
* - **Resolves** the promise with the Sender object when rhea emits the "sender_open" event.
* - **Rejects** the promise with an AmqpError when rhea emits the "sender_close" event while trying
* to create an amqp sender.
*/
createSender(options?: SenderOptions): Promise<Sender> {
return new Promise((resolve, reject) => {
const rheaSender = this._session.attach_sender(options);
const sender = new Sender(this, rheaSender, options);
let onSendable: Func<rhea.EventContext, void>;
let onClose: Func<rhea.EventContext, void>;
if (options) {
if (options.onError) {
rheaSender.on(SenderEvents.senderError, options.onError);
}
if (options.onAccepted) {
rheaSender.on(SenderEvents.accepted, options.onAccepted);
}
if (options.onRejected) {
rheaSender.on(SenderEvents.rejected, options.onRejected);
}
if (options.onReleased) {
rheaSender.on(SenderEvents.released, options.onReleased);
}
if (options.onModified) {
rheaSender.on("modified", options.onModified);
}
}
const removeListeners = () => {
rheaSender.removeListener(SenderEvents.senderOpen, onSendable);
rheaSender.removeListener(SenderEvents.senderClose, onClose);
};
onSendable = (context: rhea.EventContext) => {
removeListeners();
process.nextTick(() => {
debug(`Resolving the promise with amqp sender "${rheaSender.name}".`);
resolve(sender);
});
};
onClose = (context: rhea.EventContext) => {
removeListeners();
debug(`Error occurred while creating a sender over amqp connection.`, context.sender!.error);
reject(context.sender!.error);
};
rheaSender.once(SenderEvents.sendable, onSendable);
rheaSender.once(SenderEvents.senderClose, onClose);
});
}
registerHandler(event: rhea.SessionEvents, handler: rhea.OnAmqpEvent): void {
this._session.on(event, handler);
}
removeHandler(event: rhea.SessionEvents, handler: rhea.OnAmqpEvent): void {
this._session.removeListener(event, handler);
}
}

Просмотреть файл

@ -1,273 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as os from "os";
import * as process from "process";
import * as debugModule from "debug";
import * as uuid from "uuid/v4";
import { defaultLock } from "./util/utils";
import {
ReceiverOptions, SenderOptions, createSession, createSenderWithHandlers,
createSender, createReceiver, connect, ConnectionOptions,
OnAmqpEvent, AmqpError, createReceiverWithHandlers, EventContext, Connection
} from "./rhea-promise";
import * as Constants from "./util/constants";
import { ConditionStatusMapper, translate } from "./errors";
import { Message, ConnectionConfig } from ".";
import { retry } from "./retry";
const debug = debugModule("azure:event-hubs:rpc");
export interface RequestResponseLink {
sender: any;
receiver: any;
session: any;
}
export interface ReceiverLink {
receiver: any;
session: any;
}
export interface SenderLink {
sender: any;
session: any;
}
export interface LinkOptions {
connection: any;
onError: OnAmqpEvent;
}
export interface ReceiverLinkOptions extends LinkOptions {
onMessage: OnAmqpEvent;
receiverOptions: ReceiverOptions;
}
export interface SenderLinkOptions extends LinkOptions {
senderOptions: SenderOptions;
}
export interface CreateConnectionPrameters {
config: ConnectionConfig;
userAgent: string;
useSaslPlain?: boolean;
}
export async function createRequestResponseLink(connection: any, senderOptions: SenderOptions, receiverOptions: ReceiverOptions): Promise<RequestResponseLink> {
if (!connection) {
throw new Error(`Please provide a connection to create the sender/receiver link on the same session.`);
}
if (!senderOptions) {
throw new Error(`Please provide sender options.`);
}
if (!receiverOptions) {
throw new Error(`Please provide receiver options.`);
}
const session = await createSession(connection);
const sender = await createSender(session, senderOptions);
const receiver = await createReceiver(session, receiverOptions);
debug("[%s] Successfully created the sender and receiver links on the same session.", connection.options.id);
return {
session: session,
sender: sender,
receiver: receiver
};
}
export async function createReceiverLink(connection: any, receiverOptions: ReceiverOptions): Promise<ReceiverLink> {
if (!connection) {
throw new Error(`Please provide a connection to create the receiver link on a session.`);
}
if (!receiverOptions) {
throw new Error(`Please provide receiver options.`);
}
const session = await createSession(connection);
const receiver = await createReceiver(session, receiverOptions);
debug("[%s] Successfully created the receiver link on a dedicated session for it.", connection.options.id);
return {
session: session,
receiver: receiver
};
}
export async function createReceiverLinkWithHandlers(options: ReceiverLinkOptions): Promise<ReceiverLink> {
if (!options.connection) {
throw new Error(`Please provide a connection to create the receiver link on a session.`);
}
if (!options.receiverOptions) {
throw new Error(`Please provide receiver options.`);
}
if (!options.onError) {
throw new Error(`Please provide onError.`);
}
if (!options.onMessage) {
throw new Error(`Please provide onMessage.`);
}
const session = await createSession(options.connection);
const receiver = await createReceiverWithHandlers(session, options.onMessage, options.onError, options.receiverOptions);
debug("[%s] Successfully created the receiver link on a dedicated session for it.",
options.connection.options.id);
return {
session: session,
receiver: receiver
};
}
export async function createSenderLink(connection: any, senderOptions: SenderOptions): Promise<SenderLink> {
if (!connection) {
throw new Error(`Please provide a connection to create the sender link on a session.`);
}
if (!senderOptions) {
throw new Error(`Please provide sender options.`);
}
const session = await createSession(connection);
const sender = await createSender(session, senderOptions);
debug("[%s] Successfully created the sender link on a dedicated session for it.",
connection.options.id);
return {
session: session,
sender: sender
};
}
export async function createSenderLinkWithHandlers(options: SenderLinkOptions): Promise<SenderLink> {
if (!options.connection) {
throw new Error(`Please provide a connection to create the sender link on a session.`);
}
if (!options.senderOptions) {
throw new Error(`Please provide sender options.`);
}
if (!options.onError) {
throw new Error(`Please provide onError.`);
}
const session = await createSession(options.connection);
const sender = await createSenderWithHandlers(session, options.onError, options.senderOptions);
debug("[%s] Successfully created the sender link on a dedicated session for it.",
options.connection.options.id);
return {
session: session,
sender: sender
};
}
export function sendRequest(connection: any, link: RequestResponseLink, request: Message, timeoutInSeconds?: number): Promise<any> {
if (!connection) {
throw new Error("connection is a required parameter and must be of type 'object'.");
}
if (!link) {
throw new Error("link is a required parameter and must be of type 'object'.");
}
if (!request) {
throw new Error("request is a required parameter and must be of type 'object'.");
}
if (!request.message_id) request.message_id = uuid();
if (!timeoutInSeconds) {
timeoutInSeconds = 10;
}
const sendRequestPromise: Promise<any> = new Promise((resolve: any, reject: any) => {
let waitTimer: any;
let timeOver: boolean = false;
const messageCallback = (context: EventContext) => {
// remove the event listener as this will be registered next time when someone makes a request.
link.receiver.removeListener(Constants.message, messageCallback);
const code: number = context.message!.application_properties![Constants.statusCode];
const desc: string = context.message!.application_properties![Constants.statusDescription];
const errorCondition: string | undefined = context.message!.application_properties![Constants.errorCondition];
const responseCorrelationId = context.message!.correlation_id;
debug("[%s] %s response: ", connection.options.id, request.to || "$management", context.message);
if (code > 199 && code < 300) {
if (request.message_id === responseCorrelationId || request.correlation_id === responseCorrelationId) {
if (!timeOver) {
clearTimeout(waitTimer);
}
debug("[%s] request-messageId | '%s' == '%s' | response-correlationId.", connection.options.id, request.message_id, responseCorrelationId);
return resolve(context.message!.body);
} else {
debug("[%s] request-messageId | '%s' != '%s' | response-correlationId. Hence dropping this response and waiting for the next one.",
connection.options.id, request.message_id, responseCorrelationId);
}
} else {
const condition = errorCondition || ConditionStatusMapper[code] || "amqp:internal-error";
const e: AmqpError = {
condition: condition,
description: desc
};
return reject(translate(e));
}
};
const actionAfterTimeout = () => {
timeOver = true;
link.receiver.removeListener(Constants.message, messageCallback);
const address = link.receiver.options && link.receiver.options.source && link.receiver.options.source.address
? link.receiver.options.source.address
: "address";
const desc: string = `The request with message_id "${request.message_id}" to "${address}" ` +
`endpoint timed out. Please try again later.`;
const e: AmqpError = {
condition: ConditionStatusMapper[408],
description: desc
};
return reject(translate(e));
};
link.receiver.on(Constants.message, messageCallback);
waitTimer = setTimeout(actionAfterTimeout, timeoutInSeconds! * 1000);
debug("[%s] %s request sent: %O", connection.options.id, request.to || "$managment", request);
link.sender.send(request);
});
return retry(() => sendRequestPromise);
}
/**
* Opens the AMQP connection to the Event Hub for this client, returning a promise
* that will be resolved when the connection is completed.
*
* @param {ConnectionContext} context The connection context.
* @param {boolean} [useSaslPlain] True for using sasl plain mode for authentication, false otherwise.
* @returns {Promise<Connection>} The Connection object.
*/
export async function open(params: CreateConnectionPrameters): Promise<Connection> {
try {
return await defaultLock.acquire("connect", () => { return _open(params); });
} catch (err) {
debug(err);
throw err;
}
}
async function _open(params: CreateConnectionPrameters): Promise<Connection> {
const connectOptions: ConnectionOptions = {
transport: Constants.TLS,
host: params.config.host,
hostname: params.config.host,
username: params.config.sharedAccessKeyName,
port: 5671,
reconnect_limit: Constants.reconnectLimit,
properties: {
product: "MSJSClient",
version: Constants.packageJsonInfo.version || "0.1.0",
platform: `(${os.arch()}-${os.type()}-${os.release()})`,
framework: `Node/${process.version}`,
"user-agent": params.userAgent
}
};
if (params.useSaslPlain) {
connectOptions.password = params.config.sharedAccessKey;
}
debug("Dialing the amqp connection with options.", connectOptions);
const connection = await connect(connectOptions);
debug("Successfully established the amqp connection '%s'.", connection.options.id);
return connection;
}

Просмотреть файл

@ -3,12 +3,19 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as debugModule from "debug";
import { Constants } from "./amqp-common";
import { ReceiverEvents } from "./rhea-promise";
import { ReceiveOptions, OnMessage, OnError } from ".";
import { EventHubReceiver, ReceiverRuntimeInfo } from "./eventHubReceiver";
import { ConnectionContext } from "./connectionContext";
import * as Constants from "./util/constants";
const debug = debugModule("azure:event-hubs:receiverstreaming");
/**
* Describes the receive handler object that is returned from the receive() method with handlers is
* called. The ReceiveHandler is used to stop receiving more messages.
* @class ReceiveHandler
*/
export class ReceiveHandler {
/**
* @property {string} name The Receiver handler name.
@ -141,7 +148,7 @@ export class StreamingReceiver extends EventHubReceiver {
}
this._onMessage = onMessage;
this._onError = onError;
if (!this._isOpen()) {
if (!this.isOpen()) {
this._init().catch((err) => {
this._onError!(err);
});
@ -151,10 +158,10 @@ export class StreamingReceiver extends EventHubReceiver {
// these handlers will be automatically removed.
debug("[%s] Receiver link is already present for '%s' due to previous receive() calls. " +
"Hence reusing it and attaching message and error handlers.", this._context.connectionId, this.name);
this._receiver.on(Constants.message, this._onAmqpMessage);
this._receiver.on(Constants.receiverError, this._onAmqpError);
this._receiver.set_credit_window(Constants.defaultPrefetchCount);
this._receiver.add_credit(Constants.defaultPrefetchCount);
this._receiver!.registerHandler(ReceiverEvents.message, this._onAmqpMessage);
this._receiver!.registerHandler(ReceiverEvents.receiverError, this._onAmqpError);
this._receiver!.setCreditWindow(Constants.defaultPrefetchCount);
this._receiver!.addCredit(Constants.defaultPrefetchCount);
debug("[%s] Receiver '%s', set the prefetch count to 1000 and " +
"providing a credit of the same amount.", this._context.connectionId, this.name);
}

Просмотреть файл

@ -1,46 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
export const partitionKey = "x-opt-partition-key";
export const sequenceNumber = "x-opt-sequence-number";
export const enqueuedTime = "x-opt-enqueued-time";
export const offset = "x-opt-offset";
export const enqueuedTimeAnnotation = `amqp.annotation.${enqueuedTime}`;
export const offsetAnnotation = `amqp.annotation.${offset}`;
export const sequenceNumberAnnotation = `amqp.annotation.${sequenceNumber}`;
export const message = "message";
export const error = "error";
export const statusCode = "status-code";
export const statusDescription = "status-description";
export const errorCondition = "error-condition";
export const management = "$management";
export const partition = "partition";
export const partitionId = "partitionId";
export const readOperation = "READ";
export const TLS = "tls";
export const establishConnection = "establishConnection";
export const defaultConsumerGroup = "$default";
export const eventHub = "eventhub";
export const cbsEndpoint = "$cbs";
export const cbsReplyTo = "cbs";
export const operationPutToken = "put-token";
export const aadEventHubsAudience = "https://eventhubs.azure.net/";
export const maxUserAgentLength = 128;
export const vendorString = "com.microsoft";
export const attachEpoch = `${vendorString}:epoch`;
export const receiverIdentifierName = `${vendorString}:receiver-name`;
export const enableReceiverRuntimeMetricName = `${vendorString}:enable-receiver-runtime-metric`;
export const receiverError = "receiver_error";
export const senderError = "sender_error";
export const sessionError = "session_error";
export const connectionError = "connection_error";
export const defaultOperationTimeoutInSeconds = 60;
export const managementRequestKey = "managementRequest";
export const negotiateCbsKey = "negotiateCbs";
export const negotiateClaim = "negotiateClaim";
export const ensureContainerAndBlob = "ensureContainerAndBlob";
export const defaultPrefetchCount = 1000;
export const reconnectLimit = 100;
export const packageJsonInfo = {
name: "azure-event-hubs-js",
version: "0.2.3"

334
client/package-lock.json сгенерированный
Просмотреть файл

@ -27,7 +27,7 @@
"integrity": "sha512-MFiW54UOSt+f2bRw8J7LgQeIvE/9b4oGvwU7XW30S9QGAiHGnU/fmiOprsyMkdmH2rl8xSPc0/yrQw8juXU6bQ==",
"dev": true,
"requires": {
"@types/chai": "4.1.3"
"@types/chai": "*"
}
},
"@types/debug": {
@ -42,7 +42,7 @@
"integrity": "sha512-mmhpINC/HcLGQK5ikFJlLXINVvcxhlrV+ZOUJSN7/ottYl+8X4oSXzS9lBtDkmWAl96EGyGyLrNvk9zqdSH8Fw==",
"dev": true,
"requires": {
"@types/node": "8.9.4"
"@types/node": "*"
}
},
"@types/form-data": {
@ -50,7 +50,7 @@
"resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz",
"integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==",
"requires": {
"@types/node": "8.9.4"
"@types/node": "*"
}
},
"@types/mocha": {
@ -69,10 +69,10 @@
"resolved": "https://registry.npmjs.org/@types/request/-/request-2.47.0.tgz",
"integrity": "sha512-/KXM5oev+nNCLIgBjkwbk8VqxmzI56woD4VUxn95O+YeQ8hJzcSmIZ1IN3WexiqBb6srzDo2bdMbsXxgXNkz5Q==",
"requires": {
"@types/caseless": "0.12.1",
"@types/form-data": "2.2.1",
"@types/node": "8.9.4",
"@types/tough-cookie": "2.3.2"
"@types/caseless": "*",
"@types/form-data": "*",
"@types/node": "*",
"@types/tough-cookie": "*"
}
},
"@types/tough-cookie": {
@ -85,7 +85,7 @@
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.3.tgz",
"integrity": "sha512-5fRLCYhLtDb3hMWqQyH10qtF+Ud2JnNCXTCZ+9ktNdCcgslcuXkDTkFcJNk++MT29yDntDnlF1+jD+uVGumsbw==",
"requires": {
"@types/node": "8.9.4"
"@types/node": "*"
}
},
"adal-node": {
@ -93,15 +93,15 @@
"resolved": "https://registry.npmjs.org/adal-node/-/adal-node-0.1.28.tgz",
"integrity": "sha1-RoxLs+u9lrEnBmn0ucuk4AZepIU=",
"requires": {
"@types/node": "8.9.4",
"async": "2.6.0",
"date-utils": "1.2.21",
"jws": "3.1.5",
"request": "2.83.0",
"underscore": "1.8.3",
"uuid": "3.2.1",
"xmldom": "0.1.27",
"xpath.js": "1.1.0"
"@types/node": "^8.0.47",
"async": ">=0.6.0",
"date-utils": "*",
"jws": "3.x.x",
"request": ">= 2.52.0",
"underscore": ">= 1.3.1",
"uuid": "^3.1.0",
"xmldom": ">= 0.1.x",
"xpath.js": "~1.1.0"
}
},
"ajv": {
@ -109,10 +109,10 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
"integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
"requires": {
"co": "4.6.0",
"fast-deep-equal": "1.1.0",
"fast-json-stable-stringify": "2.0.0",
"json-schema-traverse": "0.3.1"
"co": "^4.6.0",
"fast-deep-equal": "^1.0.0",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.3.0"
}
},
"ansi-regex": {
@ -127,7 +127,7 @@
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"requires": {
"color-convert": "1.9.1"
"color-convert": "^1.9.0"
}
},
"argparse": {
@ -136,7 +136,7 @@
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"requires": {
"sprintf-js": "1.0.3"
"sprintf-js": "~1.0.2"
}
},
"arrify": {
@ -166,7 +166,7 @@
"resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz",
"integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==",
"requires": {
"lodash": "4.17.5"
"lodash": "^4.14.0"
}
},
"async-lock": {
@ -195,9 +195,9 @@
"integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
"dev": true,
"requires": {
"chalk": "1.1.3",
"esutils": "2.0.2",
"js-tokens": "3.0.2"
"chalk": "^1.1.3",
"esutils": "^2.0.2",
"js-tokens": "^3.0.2"
},
"dependencies": {
"ansi-styles": {
@ -212,11 +212,11 @@
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
"ansi-styles": "2.2.1",
"escape-string-regexp": "1.0.5",
"has-ansi": "2.0.0",
"strip-ansi": "3.0.1",
"supports-color": "2.0.0"
"ansi-styles": "^2.2.1",
"escape-string-regexp": "^1.0.2",
"has-ansi": "^2.0.0",
"strip-ansi": "^3.0.0",
"supports-color": "^2.0.0"
}
},
"supports-color": {
@ -239,7 +239,7 @@
"integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
"optional": true,
"requires": {
"tweetnacl": "0.14.5"
"tweetnacl": "^0.14.3"
}
},
"boom": {
@ -247,7 +247,7 @@
"resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz",
"integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=",
"requires": {
"hoek": "4.2.1"
"hoek": "4.x.x"
}
},
"brace-expansion": {
@ -256,7 +256,7 @@
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "1.0.0",
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
@ -294,12 +294,12 @@
"integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=",
"dev": true,
"requires": {
"assertion-error": "1.1.0",
"check-error": "1.0.2",
"deep-eql": "3.0.1",
"get-func-name": "2.0.0",
"pathval": "1.1.0",
"type-detect": "4.0.8"
"assertion-error": "^1.0.1",
"check-error": "^1.0.1",
"deep-eql": "^3.0.0",
"get-func-name": "^2.0.0",
"pathval": "^1.0.0",
"type-detect": "^4.0.0"
}
},
"chai-as-promised": {
@ -308,7 +308,7 @@
"integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==",
"dev": true,
"requires": {
"check-error": "1.0.2"
"check-error": "^1.0.2"
}
},
"chalk": {
@ -317,9 +317,9 @@
"integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
"dev": true,
"requires": {
"ansi-styles": "3.2.1",
"escape-string-regexp": "1.0.5",
"supports-color": "5.4.0"
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
}
},
"check-error": {
@ -339,7 +339,7 @@
"integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
"dev": true,
"requires": {
"color-name": "1.1.3"
"color-name": "^1.1.1"
}
},
"color-name": {
@ -353,7 +353,7 @@
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
"integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
"requires": {
"delayed-stream": "1.0.0"
"delayed-stream": "~1.0.0"
}
},
"commander": {
@ -378,7 +378,7 @@
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
"integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=",
"requires": {
"boom": "5.2.0"
"boom": "5.x.x"
},
"dependencies": {
"boom": {
@ -386,7 +386,7 @@
"resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz",
"integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==",
"requires": {
"hoek": "4.2.1"
"hoek": "4.x.x"
}
}
}
@ -396,7 +396,7 @@
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
"requires": {
"assert-plus": "1.0.0"
"assert-plus": "^1.0.0"
}
},
"date-utils": {
@ -418,7 +418,7 @@
"integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
"dev": true,
"requires": {
"type-detect": "4.0.8"
"type-detect": "^4.0.0"
}
},
"delayed-stream": {
@ -449,7 +449,7 @@
"integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
"optional": true,
"requires": {
"jsbn": "0.1.1"
"jsbn": "~0.1.0"
}
},
"ecdsa-sig-formatter": {
@ -457,7 +457,7 @@
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz",
"integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=",
"requires": {
"safe-buffer": "5.1.1"
"safe-buffer": "^5.0.1"
}
},
"escape-string-regexp": {
@ -508,9 +508,9 @@
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
"integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
"requires": {
"asynckit": "0.4.0",
"asynckit": "^0.4.0",
"combined-stream": "1.0.6",
"mime-types": "2.1.18"
"mime-types": "^2.1.12"
}
},
"fs.realpath": {
@ -530,7 +530,7 @@
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
"requires": {
"assert-plus": "1.0.0"
"assert-plus": "^1.0.0"
}
},
"glob": {
@ -539,12 +539,12 @@
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"dev": true,
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"growl": {
@ -563,8 +563,8 @@
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
"integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
"requires": {
"ajv": "5.5.2",
"har-schema": "2.0.0"
"ajv": "^5.1.0",
"har-schema": "^2.0.0"
}
},
"has-ansi": {
@ -573,7 +573,7 @@
"integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
"dev": true,
"requires": {
"ansi-regex": "2.1.1"
"ansi-regex": "^2.0.0"
}
},
"has-flag": {
@ -587,10 +587,10 @@
"resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz",
"integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==",
"requires": {
"boom": "4.3.1",
"cryptiles": "3.1.2",
"hoek": "4.2.1",
"sntp": "2.1.0"
"boom": "4.x.x",
"cryptiles": "3.x.x",
"hoek": "4.x.x",
"sntp": "2.x.x"
}
},
"he": {
@ -609,9 +609,9 @@
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
"requires": {
"assert-plus": "1.0.0",
"jsprim": "1.4.1",
"sshpk": "1.14.1"
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
"sshpk": "^1.7.0"
}
},
"inflight": {
@ -620,8 +620,8 @@
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
@ -662,8 +662,8 @@
"integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==",
"dev": true,
"requires": {
"argparse": "1.0.10",
"esprima": "4.0.0"
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"jsbn": {
@ -705,7 +705,7 @@
"requires": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.10",
"safe-buffer": "5.1.1"
"safe-buffer": "^5.0.1"
}
},
"jws": {
@ -713,8 +713,8 @@
"resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz",
"integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==",
"requires": {
"jwa": "1.1.6",
"safe-buffer": "5.1.1"
"jwa": "^1.1.5",
"safe-buffer": "^5.0.1"
}
},
"lodash": {
@ -738,7 +738,7 @@
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
"integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
"requires": {
"mime-db": "1.33.0"
"mime-db": "~1.33.0"
}
},
"minimatch": {
@ -747,7 +747,7 @@
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "1.1.11"
"brace-expansion": "^1.1.7"
}
},
"minimist": {
@ -796,7 +796,7 @@
"integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
"has-flag": "^3.0.0"
}
}
}
@ -816,17 +816,17 @@
"resolved": "https://registry.npmjs.org/ms-rest/-/ms-rest-2.3.3.tgz",
"integrity": "sha512-S45//v5VIOojBYl2eL9ynyoskKluSUGxP5LBhS0Tf1Hb/k3JgkafoXlAxT4eKj5dyUmA/pPcRBGhZNohOH2esQ==",
"requires": {
"@types/node": "8.9.4",
"@types/request": "2.47.0",
"@types/uuid": "3.4.3",
"duplexer": "0.1.1",
"is-buffer": "1.1.6",
"is-stream": "1.1.0",
"moment": "2.21.0",
"request": "2.83.0",
"through": "2.3.8",
"@types/node": "^8.9.4",
"@types/request": "^2.47.0",
"@types/uuid": "^3.4.3",
"duplexer": "^0.1.1",
"is-buffer": "^1.1.6",
"is-stream": "^1.1.0",
"moment": "^2.21.0",
"request": "^2.83.0",
"through": "^2.3.8",
"tunnel": "0.0.5",
"uuid": "3.2.1"
"uuid": "^3.2.1"
},
"dependencies": {
"is-buffer": {
@ -841,13 +841,13 @@
"resolved": "https://registry.npmjs.org/ms-rest-azure/-/ms-rest-azure-2.5.5.tgz",
"integrity": "sha512-LnP5YlgqvNAOi1x2b4Ru+1ubz/PtYQgNeH74aiolP+Sb5lpOw5ewzbncXae1pR5OWu9NGwxjX59adzSWosqnzw==",
"requires": {
"@types/node": "9.6.0",
"@types/uuid": "3.4.3",
"adal-node": "0.1.28",
"@types/node": "^9.4.6",
"@types/uuid": "^3.4.3",
"adal-node": "^0.1.27",
"async": "2.6.0",
"moment": "2.21.0",
"ms-rest": "2.3.3",
"uuid": "3.2.1"
"moment": "^2.20.1",
"ms-rest": "^2.3.2",
"uuid": "^3.2.1"
},
"dependencies": {
"@types/node": {
@ -868,7 +868,7 @@
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1.0.2"
"wrappy": "1"
}
},
"path-is-absolute": {
@ -909,28 +909,28 @@
"resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz",
"integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==",
"requires": {
"aws-sign2": "0.7.0",
"aws4": "1.6.0",
"caseless": "0.12.0",
"combined-stream": "1.0.6",
"extend": "3.0.1",
"forever-agent": "0.6.1",
"form-data": "2.3.2",
"har-validator": "5.0.3",
"hawk": "6.0.2",
"http-signature": "1.2.0",
"is-typedarray": "1.0.0",
"isstream": "0.1.2",
"json-stringify-safe": "5.0.1",
"mime-types": "2.1.18",
"oauth-sign": "0.8.2",
"performance-now": "2.1.0",
"qs": "6.5.1",
"safe-buffer": "5.1.1",
"stringstream": "0.0.6",
"tough-cookie": "2.3.4",
"tunnel-agent": "0.6.0",
"uuid": "3.2.1"
"aws-sign2": "~0.7.0",
"aws4": "^1.6.0",
"caseless": "~0.12.0",
"combined-stream": "~1.0.5",
"extend": "~3.0.1",
"forever-agent": "~0.6.1",
"form-data": "~2.3.1",
"har-validator": "~5.0.3",
"hawk": "~6.0.2",
"http-signature": "~1.2.0",
"is-typedarray": "~1.0.0",
"isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.17",
"oauth-sign": "~0.8.2",
"performance-now": "^2.1.0",
"qs": "~6.5.1",
"safe-buffer": "^5.1.1",
"stringstream": "~0.0.5",
"tough-cookie": "~2.3.3",
"tunnel-agent": "^0.6.0",
"uuid": "^3.1.0"
}
},
"resolve": {
@ -939,15 +939,15 @@
"integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==",
"dev": true,
"requires": {
"path-parse": "1.0.5"
"path-parse": "^1.0.5"
}
},
"rhea": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/rhea/-/rhea-0.2.13.tgz",
"integrity": "sha1-iIDS27RmIHYMPXEO4YhT7XRupg0=",
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/rhea/-/rhea-0.2.14.tgz",
"integrity": "sha1-8vtnWH7OsJ+gcVxHIdGsJx9R3f4=",
"requires": {
"debug": "3.1.0"
"debug": ">=0.8.0"
}
},
"safe-buffer": {
@ -966,7 +966,7 @@
"resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz",
"integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==",
"requires": {
"hoek": "4.2.1"
"hoek": "4.x.x"
}
},
"source-map": {
@ -981,8 +981,8 @@
"integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==",
"dev": true,
"requires": {
"buffer-from": "1.0.0",
"source-map": "0.6.1"
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"sprintf-js": {
@ -996,14 +996,14 @@
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz",
"integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=",
"requires": {
"asn1": "0.2.3",
"assert-plus": "1.0.0",
"bcrypt-pbkdf": "1.0.1",
"dashdash": "1.14.1",
"ecc-jsbn": "0.1.1",
"getpass": "0.1.7",
"jsbn": "0.1.1",
"tweetnacl": "0.14.5"
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
"bcrypt-pbkdf": "^1.0.0",
"dashdash": "^1.12.0",
"ecc-jsbn": "~0.1.1",
"getpass": "^0.1.1",
"jsbn": "~0.1.0",
"tweetnacl": "~0.14.0"
}
},
"stringstream": {
@ -1017,7 +1017,7 @@
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
"ansi-regex": "2.1.1"
"ansi-regex": "^2.0.0"
}
},
"supports-color": {
@ -1026,7 +1026,7 @@
"integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
"dev": true,
"requires": {
"has-flag": "3.0.0"
"has-flag": "^3.0.0"
}
},
"through": {
@ -1039,7 +1039,7 @@
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz",
"integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==",
"requires": {
"punycode": "1.4.1"
"punycode": "^1.4.1"
}
},
"ts-node": {
@ -1048,14 +1048,14 @@
"integrity": "sha512-XK7QmDcNHVmZkVtkiwNDWiERRHPyU8nBqZB1+iv2UhOG0q3RQ9HsZ2CMqISlFbxjrYFGfG2mX7bW4dAyxBVzUw==",
"dev": true,
"requires": {
"arrify": "1.0.1",
"chalk": "2.4.1",
"diff": "3.5.0",
"make-error": "1.3.4",
"minimist": "1.2.0",
"mkdirp": "0.5.1",
"source-map-support": "0.5.6",
"yn": "2.0.0"
"arrify": "^1.0.0",
"chalk": "^2.3.0",
"diff": "^3.1.0",
"make-error": "^1.1.1",
"minimist": "^1.2.0",
"mkdirp": "^0.5.1",
"source-map-support": "^0.5.3",
"yn": "^2.0.0"
},
"dependencies": {
"minimist": {
@ -1077,18 +1077,18 @@
"integrity": "sha1-EeJrzLiK+gLdDZlWyuPUVAtfVMM=",
"dev": true,
"requires": {
"babel-code-frame": "6.26.0",
"builtin-modules": "1.1.1",
"chalk": "2.4.1",
"commander": "2.15.1",
"diff": "3.5.0",
"glob": "7.1.2",
"js-yaml": "3.11.0",
"minimatch": "3.0.4",
"resolve": "1.7.1",
"semver": "5.5.0",
"tslib": "1.9.2",
"tsutils": "2.27.1"
"babel-code-frame": "^6.22.0",
"builtin-modules": "^1.1.1",
"chalk": "^2.3.0",
"commander": "^2.12.1",
"diff": "^3.2.0",
"glob": "^7.1.1",
"js-yaml": "^3.7.0",
"minimatch": "^3.0.4",
"resolve": "^1.3.2",
"semver": "^5.3.0",
"tslib": "^1.8.0",
"tsutils": "^2.12.1"
}
},
"tsutils": {
@ -1097,7 +1097,7 @@
"integrity": "sha512-AE/7uzp32MmaHvNNFES85hhUDHFdFZp6OAiZcd6y4ZKKIg6orJTm8keYWBhIhrJQH3a4LzNKat7ZPXZt5aTf6w==",
"dev": true,
"requires": {
"tslib": "1.9.2"
"tslib": "^1.8.1"
}
},
"tunnel": {
@ -1110,7 +1110,7 @@
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
"requires": {
"safe-buffer": "5.1.1"
"safe-buffer": "^5.0.1"
}
},
"tweetnacl": {
@ -1126,9 +1126,9 @@
"dev": true
},
"typescript": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.1.tgz",
"integrity": "sha512-h6pM2f/GDchCFlldnriOhs1QHuwbnmj6/v7499eMHqPeW4V2G0elua2eIc2nu8v2NdHV0Gm+tzX83Hr6nUFjQA==",
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
"integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==",
"dev": true
},
"underscore": {
@ -1146,9 +1146,9 @@
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
"requires": {
"assert-plus": "1.0.0",
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "1.3.0"
"extsprintf": "^1.2.0"
}
},
"wrappy": {

Просмотреть файл

@ -12,7 +12,7 @@
"is-buffer": "2.0.2",
"ms-rest": "^2.3.3",
"ms-rest-azure": "^2.5.5",
"rhea": "^0.2.13",
"rhea": "^0.2.14",
"tslib": "^1.9.2",
"uuid": "^3.2.1"
},
@ -31,14 +31,14 @@
"mocha": "^5.2.0",
"ts-node": "^5.0.1",
"tslint": "^5.10.0",
"typescript": "^2.9.1"
"typescript": "^2.9.2"
},
"scripts": {
"tslint": "tslint -p . -c tslint.json --exclude examples/**/*.ts --exclude tests/**/*.ts",
"tsc": "tsc",
"build": "npm run tslint && npm run tsc",
"test": "npm run build",
"unit": "mocha -r ts-node/register -t 50000 ./tests/**/*.spec.ts --abort-on-uncaught-exception",
"unit": "nyc --reporter=lcov --reporter=text-lcov mocha -r ts-node/register -t 50000 ./tests/**/*.spec.ts --abort-on-uncaught-exception",
"prepare": "npm run build"
},
"homepage": "http://github.com/azure/azure-event-hubs-node",

Просмотреть файл

@ -25,7 +25,7 @@ describe("EventHubClient", function () {
config[prop] = falsyVal;
return new EventHubClient(config as any);
};
test.should.throw(Error, `'${prop}' is a required property of the ConnectionConfig.`);
test.should.throw(Error, `'${prop}' is a required property of ConnectionConfig.`);
});
});
});

Просмотреть файл

@ -4,7 +4,7 @@
import * as chai from "chai";
chai.should();
import * as Errors from "../lib/errors";
import * as Errors from "../lib/amqp-common/errors";
class AMQPError {
name = "AmqpProtocolError";
@ -21,9 +21,9 @@ describe("Errors", function () {
describe("translate", function () {
it("acts as a passthrough if the input is not an AmqpProtocolError", function () {
const MyError = function () { };
const err = new MyError();
const err: any = new MyError();
const msg: any = undefined;
const ehError = new Errors.EventHubsError(msg);
const ehError = new Errors.MessagingError(msg);
const translatedError = Errors.translate(err);
translatedError.name.should.equal(ehError.name);
translatedError.retryable.should.equal(ehError.retryable);
@ -31,14 +31,14 @@ describe("Errors", function () {
});
[
{ from: "amqp:not-found", to: "EventHubsCommunicationError", message: "some message" },
{ from: "amqp:not-found", to: "ServiceCommunicationError", message: "some message" },
{ from: "com.microsoft:server-busy", to: "ServerBusyError", message: "some message" },
{ from: "com.microsoft:argument-out-of-range", to: "ArgumentOutOfRangeError", message: "some message" },
{ from: "<unknown>", to: "EventHubsError" }
{ from: "<unknown>", to: "MessagingError" }
]
.forEach(function (mapping) {
it("translates " + mapping.from + " into " + mapping.to, function () {
const err = new AMQPError(mapping.from, mapping.message);
const err: any = new AMQPError(mapping.from, mapping.message);
const translatedError = Errors.translate(err);
translatedError.name.should.equal(mapping.to);
if (translatedError.name === "ServerBusyError") {

Просмотреть файл

@ -4,7 +4,7 @@
import * as chai from "chai";
chai.should();
import { EventData, AmqpMessage } from "../lib";
import { EventData, Message } from "../lib";
const testAnnotations = {
"x-opt-enqueued-time": Date.now(),
@ -23,7 +23,7 @@ const applicationProperties = {
propKey: "propValue"
};
const testMessage: AmqpMessage = {
const testMessage: Message = {
body: testBody,
message_annotations: testAnnotations,
message_id: "test_id",

Просмотреть файл

@ -1,11 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
import { retry } from "../lib/retry";
import { retry, translate } from "../lib/amqp-common/";
import * as chai from "chai";
import { delay, EventHubsError } from "../lib";
import { delay, MessagingError } from "../lib";
import * as debugModule from "debug";
import { translate } from "../lib/errors";
const debug = debugModule("azure:event-hubs:retry-spec");
const should = chai.should();
@ -42,7 +41,7 @@ describe("retry function", function () {
await retry(operation);
} catch (err) {
should.exist(err);
should.equal(true, err instanceof EventHubsError);
should.equal(true, err instanceof MessagingError);
err.message.should.equal("I would like to fail.");
counter.should.equal(1);
}
@ -83,11 +82,11 @@ describe("retry function", function () {
await delay(200);
debug("counter: %d", ++counter);
if (counter == 1) {
const e = new EventHubsError("A retryable error.");
const e = new MessagingError("A retryable error.");
e.retryable = true;
throw e;
} else if (counter == 2) {
const e = new EventHubsError("A retryable error.");
const e = new MessagingError("A retryable error.");
e.retryable = true;
throw e;
} else {
@ -114,11 +113,11 @@ describe("retry function", function () {
await delay(200);
debug("counter: %d", ++counter);
if (counter == 1) {
const e = new EventHubsError("A retryable error.");
const e = new MessagingError("A retryable error.");
e.retryable = true;
throw e;
} else if (counter == 2) {
const e = new EventHubsError("A retryable error.");
const e = new MessagingError("A retryable error.");
e.retryable = true;
throw e;
} else {
@ -128,7 +127,7 @@ describe("retry function", function () {
await retry(operation, 3, 0.5);
} catch (err) {
should.exist(err);
should.equal(true, err instanceof EventHubsError);
should.equal(true, err instanceof MessagingError);
err.message.should.equal("I would like to fail.");
counter.should.equal(3);
}
@ -140,14 +139,14 @@ describe("retry function", function () {
const operation = async () => {
debug("counter: %d", ++counter);
await delay(200);
const e = new EventHubsError("I would always like to fail, keep retrying.");
const e = new MessagingError("I would always like to fail, keep retrying.");
e.retryable = true;
throw e;
};
await retry(operation, 4, 0.5);
} catch (err) {
should.exist(err);
should.equal(true, err instanceof EventHubsError);
should.equal(true, err instanceof MessagingError);
err.message.should.equal("I would always like to fail, keep retrying.");
counter.should.equal(4);
}