зеркало из
1
0
Форкнуть 0
This commit is contained in:
Amar Zavery 2018-05-02 13:11:33 -07:00
Родитель 6b4b66192a
Коммит 0251e9326d
103 изменённых файлов: 5931 добавлений и 337 удалений

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

@ -2,4 +2,16 @@ language: node_js
sudo: false
node_js:
- "8"
- "6"
- "6"
before_install:
- cd client
- npm install
- cd ../processor
- npm install
script:
- cd client
- npm test
- cd ../processor
- npm test

181
README.md
Просмотреть файл

@ -1,178 +1,5 @@
azure-event-hubs
================
# Azure Event Hubs for node.js
_This SDK is currently in preview._
- **Node.js version: 6.x or higher.** We would encourage you to install the latest available LTS version from https://nodejs.org.
## Installation ##
```bash
npm install azure-event-hubs
```
### Client creation
The simplest usage is to use the static factory method `EventHubClient.createFromConnectionString(_connection-string_, _event-hub-path_)`. Once you have a client, you can use it for:
### Sending events
- You can send a single event using `client.send()` method.
- You can even batch multiple events together using `client.sendBatch()` method.
### Receiving events
- You can use `await client.receiveBatch(...)` to receive desired number of events for specified amount of time. **Note this is a blocking call**.
That is it will return an array of EventData objects once it receives the desired number of events or the max wait time occurs (which ever happens first).
This is very useful when you want to know how the received events look like or for testing/debugging purposes.
- For production we would expect customers would simply want to receive events and process them. Hence we have a `client.receive(. . .)` method on the receiver.
This message takes the `messageHandler()` and the `errorHandler()` amongst other parameters and registers them to the receiver.
This method returns a `ReceiverHandler` that can be used to stop receiving further events `await receiverHandler.stop()`
## IDE ##
This sdk has been developed in [TypeScript](https://typescriptlang.org) and has good source code documentation. It is highly recommended to use [vscode](https://code.visualstudio.com) or any other IDE that provides better intellisense and exposes the full power of source code documentation.
## Debug logs ##
You can set the following environment variable to get the debug logs.
- Getting the debug logs from the Event Hub SDK
```
export DEBUG=azure*
```
- Getting the debug logs from the Event Hub SDK and the protocol level library.
```
export DEBUG=azure*,rhea*
```
## Examples ##
Please take a look at the [examples](https://github.com/Azure/azure-event-hubs-node/tree/master/examples) directory for detailed examples.
## Example 1 - Get the partition IDs.
```js
const { EventHubClient } = require('azure-event-hubs');
const client = EventHubClient.createFromConnectionString(process.env["EVENTHUB_CONNECTION_STRING"], process.env["EVENTHUB_NAME"]);
async function main() {
const partitionIds = await client.getPartitionIds();
}
main().catch((err) => {
console.log(err);
});
```
## Example 2.1 - Receive events with handlers
This mechanism is useful for receiving events for a longer duration.
Receive events from partition ID 1 after the current time.
```js
const { EventHubClient, EventPosition } = require('azure-event-hubs');
const client = EventHubClient.createFromConnectionString(process.env["EVENTHUB_CONNECTION_STRING"], process.env["EVENTHUB_NAME"]);
async function main() {
const onError = (err) => {
console.log("An error occurred on the receiver ", err);
};
const onMessage = (eventData) => {
console.log(eventData.body);
const enqueuedTime = eventData.annotations["x-opt-enqueued-time"];
console.log("Enqueued Time: ", enqueuedTime);
};
const receiveHandler = client.receive("1", onMessage, onError, { eventPosition: EventPosition.fromEnqueuedTime(Date.now()) });
// To stop receiving events later on...
await receiveHandler.stop();
}
main().catch((err) => {
console.log(err);
});
```
## Example 2.2 - Receive specified number of events for a given time
This mechanism is useful when you want to see how the received events look like. It can also be useful for debugging purpose.
Receive events from partitionId `"1"` after the current time.
```js
const { EventHubClient, EventPosition } = require('azure-event-hubs');
const client = EventHubClient.createFromConnectionString(process.env["EVENTHUB_CONNECTION_STRING"], process.env["EVENTHUB_NAME"]);
async function main() {
const datas = await client.receiveBatch("1", 100 /*number of events*/, 20 /*amount of time in seconds the receiver should run. Default 60 seconds.*/, { eventPosition: EventPosition.fromEnqueuedTime(Date.now()) });
console.log("Array of EventData objects", datas);
}
main().catch((err) => {
console.log(err);
});
```
## Example 3 - Send an event with partition key.
Send an event with a given "partition-key" which is then hashed to a partition ID (so all events with the same key will go to the same ID, but load is balanced between partitions).
```js
const { EventHubClient, EventPosition } = require('azure-event-hubs');
const client = EventHubClient.createFromConnectionString(process.env["EVENTHUB_CONNECTION_STRING"], process.env["EVENTHUB_NAME"]);
async function main() {
const eventData: EventData = { body: "Hello World", partitionKey: "pk12345"};
const delivery = await client.send(eventData);
console.log("message sent successfully.");
}
main().catch((err) => {
console.log(err);
});
```
## Example 4 - Send an event to a specific partition id.
Send an event to a specific partition ID if needed. If not specified then EventHub will store the events in the partition in a round-robin pattern.
```js
const { EventHubClient, EventPosition } = require('azure-event-hubs');
const client = EventHubClient.createFromConnectionString(process.env["EVENTHUB_CONNECTION_STRING"], process.env["EVENTHUB_NAME"]);
async function main() {
const data: EventData = { body: "Hello World 1", message_id: "343-0909-5454-23423-54543" };
const delivery = await client.send(data, "1");
console.log("message sent successfully.");
}
main().catch((err) => {
console.log(err);
});
```
## Example 5 - Send multiple events as a batch.
Send multiple events grouped together.
```js
const { EventHubClient, EventPosition } = require('azure-event-hubs');
const client = EventHubClient.createFromConnectionString(process.env["EVENTHUB_CONNECTION_STRING"], process.env["EVENTHUB_NAME"]);
async function main() {
const datas = [
{ body: "Hello World 1", applicationProperties: { id: "Some id" }, partitionKey: "pk786" },
{ body: "Hello World 2" },
{ body: "Hello World 3" }
];
const delivery = await client.sendBatch(datas);
console.log("message sent successfully.");
}
main().catch((err) => {
console.log(err);
});
```
## AMQP Dependencies ##
It depends on [rhea](https://github.com/amqp/rhea) library for managing connections, sending and receiving events over the [AMQP](http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-complete-v1.0-os.pdf) protocol.
We have two client libraries for Azure Event Hubs
- `azure-event-hubs` - The Event Hubs client for sending and receiving messages. You can find more info over [here](./client).
- `azure-event-processor-host` - The Event Processor Host for efficient receiving of messages. You can find more info over [here](./processor).

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

178
client/README.md Normal file
Просмотреть файл

@ -0,0 +1,178 @@
azure-event-hubs
================
_This SDK is currently in preview._
- **Node.js version: 6.x or higher.** We would encourage you to install the latest available LTS version from https://nodejs.org.
## Installation ##
```bash
npm install azure-event-hubs
```
### Client creation
The simplest usage is to use the static factory method `EventHubClient.createFromConnectionString(_connection-string_, _event-hub-path_)`. Once you have a client, you can use it for:
### Sending events
- You can send a single event using `client.send()` method.
- You can even batch multiple events together using `client.sendBatch()` method.
### Receiving events
- You can use `await client.receiveBatch(...)` to receive desired number of events for specified amount of time. **Note this is a blocking call**.
That is it will return an array of EventData objects once it receives the desired number of events or the max wait time occurs (which ever happens first).
This is very useful when you want to know how the received events look like or for testing/debugging purposes.
- For production we would expect customers would simply want to receive events and process them. Hence we have a `client.receive(. . .)` method on the receiver.
This message takes the `messageHandler()` and the `errorHandler()` amongst other parameters and registers them to the receiver.
This method returns a `ReceiverHandler` that can be used to stop receiving further events `await receiverHandler.stop()`
## IDE ##
This sdk has been developed in [TypeScript](https://typescriptlang.org) and has good source code documentation. It is highly recommended to use [vscode](https://code.visualstudio.com) or any other IDE that provides better intellisense and exposes the full power of source code documentation.
## Debug logs ##
You can set the following environment variable to get the debug logs.
- Getting the debug logs from the Event Hub SDK
```
export DEBUG=azure*
```
- Getting the debug logs from the Event Hub SDK and the protocol level library.
```
export DEBUG=azure*,rhea*
```
## Examples ##
Please take a look at the [examples](https://github.com/Azure/azure-event-hubs-node/tree/master/examples) directory for detailed examples.
## Example 1 - Get the partition IDs.
```js
const { EventHubClient } = require('azure-event-hubs');
const client = EventHubClient.createFromConnectionString(process.env["EVENTHUB_CONNECTION_STRING"], process.env["EVENTHUB_NAME"]);
async function main() {
const partitionIds = await client.getPartitionIds();
}
main().catch((err) => {
console.log(err);
});
```
## Example 2.1 - Receive events with handlers
This mechanism is useful for receiving events for a longer duration.
Receive events from partition ID 1 after the current time.
```js
const { EventHubClient, EventPosition } = require('azure-event-hubs');
const client = EventHubClient.createFromConnectionString(process.env["EVENTHUB_CONNECTION_STRING"], process.env["EVENTHUB_NAME"]);
async function main() {
const onError = (err) => {
console.log("An error occurred on the receiver ", err);
};
const onMessage = (eventData) => {
console.log(eventData.body);
const enqueuedTime = eventData.annotations["x-opt-enqueued-time"];
console.log("Enqueued Time: ", enqueuedTime);
};
const receiveHandler = client.receive("1", onMessage, onError, { eventPosition: EventPosition.fromEnqueuedTime(Date.now()) });
// To stop receiving events later on...
await receiveHandler.stop();
}
main().catch((err) => {
console.log(err);
});
```
## Example 2.2 - Receive specified number of events for a given time
This mechanism is useful when you want to see how the received events look like. It can also be useful for debugging purpose.
Receive events from partitionId `"1"` after the current time.
```js
const { EventHubClient, EventPosition } = require('azure-event-hubs');
const client = EventHubClient.createFromConnectionString(process.env["EVENTHUB_CONNECTION_STRING"], process.env["EVENTHUB_NAME"]);
async function main() {
const datas = await client.receiveBatch("1", 100 /*number of events*/, 20 /*amount of time in seconds the receiver should run. Default 60 seconds.*/, { eventPosition: EventPosition.fromEnqueuedTime(Date.now()) });
console.log("Array of EventData objects", datas);
}
main().catch((err) => {
console.log(err);
});
```
## Example 3 - Send an event with partition key.
Send an event with a given "partition-key" which is then hashed to a partition ID (so all events with the same key will go to the same ID, but load is balanced between partitions).
```js
const { EventHubClient, EventPosition } = require('azure-event-hubs');
const client = EventHubClient.createFromConnectionString(process.env["EVENTHUB_CONNECTION_STRING"], process.env["EVENTHUB_NAME"]);
async function main() {
const eventData: EventData = { body: "Hello World", partitionKey: "pk12345"};
const delivery = await client.send(eventData);
console.log("message sent successfully.");
}
main().catch((err) => {
console.log(err);
});
```
## Example 4 - Send an event to a specific partition id.
Send an event to a specific partition ID if needed. If not specified then EventHub will store the events in the partition in a round-robin pattern.
```js
const { EventHubClient, EventPosition } = require('azure-event-hubs');
const client = EventHubClient.createFromConnectionString(process.env["EVENTHUB_CONNECTION_STRING"], process.env["EVENTHUB_NAME"]);
async function main() {
const data: EventData = { body: "Hello World 1", message_id: "343-0909-5454-23423-54543" };
const delivery = await client.send(data, "1");
console.log("message sent successfully.");
}
main().catch((err) => {
console.log(err);
});
```
## Example 5 - Send multiple events as a batch.
Send multiple events grouped together.
```js
const { EventHubClient, EventPosition } = require('azure-event-hubs');
const client = EventHubClient.createFromConnectionString(process.env["EVENTHUB_CONNECTION_STRING"], process.env["EVENTHUB_NAME"]);
async function main() {
const datas = [
{ body: "Hello World 1", applicationProperties: { id: "Some id" }, partitionKey: "pk786" },
{ body: "Hello World 2" },
{ body: "Hello World 3" }
];
const delivery = await client.sendBatch(datas);
console.log("message sent successfully.");
}
main().catch((err) => {
console.log(err);
});
```
## AMQP Dependencies ##
It depends on [rhea](https://github.com/amqp/rhea) library for managing connections, sending and receiving events over the [AMQP](http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-complete-v1.0-os.pdf) protocol.

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

@ -1,3 +1,10 @@
## 2018-05-02 0.2.0
- Added functionality to encode/decode the messages sent and received.
- Created an options object in the `client.createFromConnectionString()` and the `EventHubClient` constructor. This is a breaking change. However moving to an options object design reduces the chances of breaking changes in the future.
This options object will:
- have the existing optional `tokenProvider` property
- and a new an optional property named `dataTransformer`. You can provide your own transformer. If not provided then we will use the [DefaultDataTransformer](./client/lib/dataTransformer.ts). This should be applicable for majority of the scenarios and will ensure that messages are interoperable between different Azure services. It fixes issue #60.
## 2018-04-26 0.1.2
- Added missing dependency for `uuid` package and nit fixes in the README.md

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

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

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

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

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

@ -0,0 +1,66 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
const ms_rest_azure_1 = require("ms-rest-azure");
const token_1 = require("./token");
const Constants = require("../util/constants");
/**
* Defines the AAD (Azure ActiveDirectory) TokenProvider.
* @class AadTokenProvider
*/
class AadTokenProvider {
constructor(credentials) {
/**
* @property {number} tokenRenewalMarginInSeconds - The number of seconds within which it is
* good to renew the token. A constant set to 270 seconds (4.5 minutes). Adal has a set window of 5 minutes
* when it refreshes the token from its token cache.
*/
this.tokenRenewalMarginInSeconds = 270;
/**
* @property {number} tokenValidTimeInSeconds - The number of seconds for which the
* token is valid. A constant set to 3599 seconds (~1 hour). Adal has a set valid time of
* 1 hour (3600 seconds) when it refreshes the access token.
*/
this.tokenValidTimeInSeconds = 3599;
if (!credentials ||
(credentials &&
!(credentials instanceof ms_rest_azure_1.ApplicationTokenCredentials ||
credentials instanceof ms_rest_azure_1.UserTokenCredentials ||
credentials instanceof ms_rest_azure_1.DeviceTokenCredentials ||
credentials instanceof ms_rest_azure_1.MSITokenCredentials))) {
throw new Error("'credentials' is a required parameter and must be an instance of ApplicationTokenCredentials | UserTokenCredentials | DeviceTokenCredentials | MSITokenCredentials.");
}
if (credentials instanceof ms_rest_azure_1.MSITokenCredentials) {
credentials.resource = Constants.aadEventHubsAudience;
}
this.credentials = credentials;
}
/**
* Gets the jwt token for the specified audience
* @param {string} [audience] - The audience for which the token is desired. If not
* provided then the Endpoint from the connection string will be applied.
*/
getToken(audience) {
const self = this;
return new Promise((resolve, reject) => {
self.credentials.getToken((err, result) => {
if (err) {
reject(err);
}
let expiresOn = Date.now();
if (result.expiresOn && result.expiresOn instanceof Date) {
expiresOn = result.expiresOn.getTime();
}
const expiry = Math.floor(expiresOn / 1000) + self.tokenValidTimeInSeconds - 5;
const tokenObj = {
expiry: expiry,
tokenType: token_1.TokenType.CbsTokenTypeJwt,
token: result.accessToken
};
resolve(tokenObj);
});
});
}
}
exports.AadTokenProvider = AadTokenProvider;

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

@ -0,0 +1,76 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const crypto = require("crypto");
const utils_1 = require("../util/utils");
const token_1 = require("./token");
/**
* @class SasTokenProvider
* Defines the SasTokenProvider.
*/
class SasTokenProvider {
/**
* 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
*/
constructor(namespace, keyName, key, tokenValidTimeInSeconds, tokenRenewalMarginInSeconds) {
this.namespace = namespace;
this.keyName = keyName;
this.key = key;
this.tokenValidTimeInSeconds = tokenValidTimeInSeconds || 3600;
this.tokenRenewalMarginInSeconds = tokenRenewalMarginInSeconds || 900;
if (this.tokenValidTimeInSeconds <= this.tokenRenewalMarginInSeconds) {
throw new Error('tokenRenewalMarginInSeconds must be less than tokenValidTimeInSeconds');
}
}
/**
* Gets the sas token for the specified audience
* @param {string} [audience] - The audience for which the token is desired. If not
* provided then the Endpoint from the connection string will be applied.
*/
getToken(audience) {
return __awaiter(this, void 0, void 0, function* () {
return this._createToken(Math.floor(Date.now() / 1000) + this.tokenValidTimeInSeconds, audience);
});
}
/**
* Creates the sas token based on the provided information
* @param {string | number} expiry - The time period in unix time after which the token will expire.
* @param {string} [audience] - The audience for which the token is desired. If not
* provided then the Endpoint from the connection string will be applied.
*/
_createToken(expiry, audience) {
if (!audience)
audience = this.namespace;
audience = encodeURIComponent(audience);
const keyName = encodeURIComponent(this.keyName);
const stringToSign = audience + '\n' + expiry;
const sig = encodeURIComponent(crypto.createHmac('sha256', this.key).update(stringToSign, 'utf8').digest('base64'));
return {
token: `SharedAccessSignature sr=${audience}&sig=${sig}&se=${expiry}&skn=${keyName}`,
tokenType: token_1.TokenType.CbsTokenTypeSas,
expiry: expiry
};
}
/**
*
* @param {string} connectionString - The EventHub connection string
*/
static fromConnectionString(connectionString) {
const parsed = utils_1.parseConnectionString(connectionString);
return new SasTokenProvider(parsed.Endpoint, parsed.SharedAccessKeyName, parsed.SharedAccessKey);
}
}
exports.SasTokenProvider = SasTokenProvider;

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

@ -0,0 +1,20 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Describes the type of supported tokens.
* @readonly
* @enum TokenType
*/
var TokenType;
(function (TokenType) {
/**
* The "jwt" token type. Used with AADTokenProvider.
*/
TokenType["CbsTokenTypeJwt"] = "jwt";
/**
* The sas token type. Used with SasTokenProvider.
*/
TokenType["CbsTokenTypeSas"] = "servicebus.windows.net:sastoken";
})(TokenType = exports.TokenType || (exports.TokenType = {}));

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

@ -0,0 +1,142 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
const debugModule = require("debug");
const _1 = require(".");
const eventHubReceiver_1 = require("./eventHubReceiver");
const errors_1 = require("./errors");
const Constants = require("./util/constants");
const debug = debugModule("azure:event-hubs:receiverbatching");
/**
* Describes the batching receiver where the user can receive a specified number of messages for a predefined time.
* @class BatchingReceiver
* @extends EventHubReceiver
*/
class BatchingReceiver extends eventHubReceiver_1.EventHubReceiver {
/**
* Instantiate a new receiver from the AMQP `Receiver`. Used by `EventHubClient`.
*
* @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.
*/
constructor(context, partitionId, options) {
super(context, partitionId, options);
}
/**
* Receive a batch of EventData objects from an EventHub partition for a given count and a given max wait time in seconds, whichever
* happens first. This method can be used directly after creating the receiver object and **MUST NOT** be used along with the `start()` method.
*
* @param {number} maxMessageCount The maximum message count. Must be a value greater than 0.
* @param {number} [maxWaitTimeInSeconds] The maximum wait time in seconds for which the Receiver should wait
* to receiver the said amount of messages. If not provided, it defaults to 60 seconds.
* @returns {Promise<EventData[]>} A promise that resolves with an array of EventData objects.
*/
receive(maxMessageCount, maxWaitTimeInSeconds) {
if (!maxMessageCount || (maxMessageCount && typeof maxMessageCount !== 'number')) {
throw new Error("'maxMessageCount' is a required parameter of type number with a value greater than 0.");
}
if (maxWaitTimeInSeconds == undefined) {
maxWaitTimeInSeconds = Constants.defaultOperationTimeoutInSeconds;
}
const eventDatas = [];
let timeOver = false;
return new Promise((resolve, reject) => {
let onReceiveMessage;
let onReceiveError;
let waitTimer;
let actionAfterWaitTimeout;
// Final action to be performed after maxMessageCount is reached or the maxWaitTime is over.
const finalAction = (timeOver, data) => {
// Resetting the mode. Now anyone can call start() or receive() again.
this._receiver.removeListener(Constants.receiverError, onReceiveError);
this._receiver.removeListener(Constants.message, onReceiveMessage);
if (!data) {
data = eventDatas.length ? eventDatas[eventDatas.length - 1] : undefined;
}
if (!timeOver) {
clearTimeout(waitTimer);
}
if (this.receiverRuntimeMetricEnabled && data) {
this.runtimeInfo.lastSequenceNumber = data.lastSequenceNumber;
this.runtimeInfo.lastEnqueuedTimeUtc = data.lastEnqueuedTime;
this.runtimeInfo.lastEnqueuedOffset = data.lastEnqueuedOffset;
this.runtimeInfo.retrievalTime = data.retrievalTime;
}
resolve(eventDatas);
};
// Action to be performed after the max wait time is over.
actionAfterWaitTimeout = () => {
timeOver = true;
return finalAction(timeOver);
};
// Action to be performed on the "message" event.
onReceiveMessage = (context) => {
const data = _1.EventData.fromAmqpMessage(context.message);
if (eventDatas.length <= maxMessageCount) {
eventDatas.push(data);
}
if (eventDatas.length === maxMessageCount) {
finalAction(timeOver, data);
}
};
// Action to be taken when an error is received.
onReceiveError = (context) => {
this._receiver.removeListener(Constants.receiverError, onReceiveError);
this._receiver.removeListener(Constants.message, onReceiveMessage);
const error = errors_1.translate(context.receiver.error);
debug("[%s] Receiver '%s' received an error:\n%O", this._context.connectionId, this.name, error);
if (waitTimer) {
clearTimeout(waitTimer);
}
reject(error);
};
const addCreditAndSetTimer = (reuse) => {
debug("[%s] Receiver '%s', adding credit for receiving %d messages.", this._context.connectionId, this.name, maxMessageCount);
this._receiver.add_credit(maxMessageCount);
let msg = "[%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 * 1000);
};
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);
}
});
}
/**
* Creates a batching receiver.
* @static
*
* @param {ConnectionContext} context The connection context.
* @param {string | number} partitionId The partitionId to receive events from.
* @param {ReceiveOptions} [options] Receive options.
*/
static create(context, partitionId, options) {
const bReceiver = new BatchingReceiver(context, partitionId, options);
context.receivers[bReceiver.name] = bReceiver;
return bReceiver;
}
}
exports.BatchingReceiver = BatchingReceiver;

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

@ -0,0 +1,122 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const rpc_1 = require("./rpc");
const rheaPromise = require("./rhea-promise");
const uuid = require("uuid/v4");
const Constants = require("./util/constants");
const debugModule = require("debug");
const errors_1 = require("./errors");
const debug = debugModule("azure:event-hubs:cbs");
/**
* @class CbsClient
* Describes the EventHub Cbs client that talks to the $cbs endopint over AMQP connection.
*/
class CbsClient {
constructor() {
/**
* @property {string} endpoint CBS endpoint - "$cbs"
*/
this.endpoint = Constants.cbsEndpoint;
/**
* @property {string} replyTo CBS replyTo - The reciever link name that the service should reply to.
*/
this.replyTo = `${Constants.cbsReplyTo}-${uuid()}`;
/**
* @property {string} cbsLock The unqiue lock name per $cbs session per connection that is used to
* acquire the lock for establishing a cbs session if one does not exist for an aqmp connection.
*/
this.cbsLock = `${Constants.negotiateCbsKey}-${uuid()}`;
}
/**
* Creates a singleton instance of the CBS session if it hasn't been initialized previously on the given connection.
* @param {any} connection The AMQP connection object on which the CBS session needs to be initialized.
*/
init(connection) {
return __awaiter(this, void 0, void 0, function* () {
if (!this._cbsSenderReceiverLink) {
const rxOpt = {
source: {
address: this.endpoint
},
name: this.replyTo
};
this._cbsSenderReceiverLink = yield rpc_1.createRequestResponseLink(connection, { target: { address: this.endpoint } }, rxOpt);
this._cbsSenderReceiverLink.sender.on("sender_error", (context) => {
const ehError = errors_1.translate(context.sender.error);
debug("An error occurred on the cbs sender link.. %O", ehError);
});
this._cbsSenderReceiverLink.receiver.on("receiver_error", (context) => {
const ehError = errors_1.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.", connection.options.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.", connection.options.id, this._cbsSenderReceiverLink.sender.name, this._cbsSenderReceiverLink.receiver.name);
}
});
}
/**
* Negotiates the CBS claim with the EventHub Service.
* @param {string} audience The audience for which the token is requested.
* @param {any} connection The underlying AMQP connection.
* @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
* and rejects when an error occurs during $cbs authentication.
*/
negotiateClaim(audience, connection, tokenObject) {
return __awaiter(this, void 0, void 0, function* () {
try {
const request = {
body: tokenObject.token,
message_id: uuid(),
reply_to: this.replyTo,
to: this.endpoint,
application_properties: {
operation: Constants.operationPutToken,
name: audience,
type: tokenObject.tokenType
}
};
const response = yield rpc_1.sendRequest(connection, this._cbsSenderReceiverLink, request);
return response;
}
catch (err) {
debug("[%s]An error occurred while negotating the cbs claim: %O", connection.options.id, err);
throw err;
}
});
}
/**
* Closes the AMQP cbs session to the Event Hub for this client,
* returning a promise that will be resolved when disconnection is completed.
* @return {Promise<void>}
*/
close() {
return __awaiter(this, void 0, void 0, function* () {
try {
if (this._cbsSenderReceiverLink) {
yield rheaPromise.closeSession(this._cbsSenderReceiverLink.session);
debug("Successfully closed the cbs session.");
this._cbsSenderReceiverLink = undefined;
}
}
catch (err) {
const msg = `An error occurred while closing the cbs session: ${JSON.stringify(err)} `;
debug(msg);
throw new Error(msg);
}
});
}
}
exports.CbsClient = CbsClient;

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

@ -0,0 +1,54 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("./util/utils");
var ConnectionConfig;
(function (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
*/
function create(connectionString, path) {
if (!connectionString || (connectionString && typeof connectionString !== "string")) {
throw new Error("'connectionString' is a required parameter and must be of type: 'string'.");
}
const parsedCS = utils_1.parseConnectionString(connectionString);
if (!path && !parsedCS.EntityPath) {
throw new Error(`Either provide "path" or the "connectionString": "${connectionString}", must contain EntityPath="<path-to-the-entity>".`);
}
const result = {
connectionString: connectionString,
endpoint: parsedCS.Endpoint,
host: (parsedCS && parsedCS.Endpoint) ? (parsedCS.Endpoint.match('sb://([^/]*)') || [])[1] : "",
entityPath: path || parsedCS.EntityPath,
sharedAccessKeyName: parsedCS.SharedAccessKeyName,
sharedAccessKey: parsedCS.SharedAccessKey
};
return result;
}
ConnectionConfig.create = create;
/**
* Validates the properties of connection config.
* @param {ConnectionConfig} config The connection config to be validated.
*/
function validate(config) {
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.");
}
if (!config.entityPath || (config.entityPath && typeof config.entityPath !== "string")) {
throw new Error("'entityPath' is a required property of the ConnectionConfig.");
}
if (!config.sharedAccessKeyName || (config.sharedAccessKeyName && typeof config.sharedAccessKeyName !== "string")) {
throw new Error("'sharedAccessKeyName' is a required property of the ConnectionConfig.");
}
if (!config.sharedAccessKey || (config.sharedAccessKey && typeof config.sharedAccessKey !== "string")) {
throw new Error("'sharedAccessKey' is a required property of the ConnectionConfig.");
}
}
ConnectionConfig.validate = validate;
})(ConnectionConfig = exports.ConnectionConfig || (exports.ConnectionConfig = {}));

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

@ -0,0 +1,35 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
const debugModule = require("debug");
const uuid = require("uuid/v4");
const Constants = require("./util/constants");
const _1 = require(".");
const managementClient_1 = require("./managementClient");
const cbs_1 = require("./cbs");
const sas_1 = require("./auth/sas");
const debug = debugModule("azure:event-hubs:connectionContext");
var ConnectionContext;
(function (ConnectionContext) {
/**
* @property {string} userAgent The user agent string for the event hub client. Constant value: "/js-event-hubs".
*/
ConnectionContext.userAgent = "/js-event-hubs";
function create(config, tokenProvider) {
_1.ConnectionConfig.validate(config);
const context = {
connectionLock: `${Constants.establishConnection}-${uuid()}`,
negotiateClaimLock: `${Constants.negotiateClaim}-${uuid()}`,
config: config,
tokenProvider: tokenProvider || new sas_1.SasTokenProvider(config.endpoint, config.sharedAccessKeyName, config.sharedAccessKey),
cbsSession: new cbs_1.CbsClient(),
managementSession: new managementClient_1.ManagementClient(config.entityPath),
senders: {},
receivers: {}
};
debug("Created connection context: %O", context);
return context;
}
ConnectionContext.create = create;
})(ConnectionContext = exports.ConnectionContext || (exports.ConnectionContext = {}));

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

@ -0,0 +1,378 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
/**
* Maps the conditions to the numeric AMQP Response status codes.
* @enum {ConditionStatusMapper}
*/
var ConditionStatusMapper;
(function (ConditionStatusMapper) {
ConditionStatusMapper[ConditionStatusMapper["com.microsoft:timeout"] = 408] = "com.microsoft:timeout";
ConditionStatusMapper[ConditionStatusMapper["amqp:not-found"] = 404] = "amqp:not-found";
ConditionStatusMapper[ConditionStatusMapper["amqp:not-implemented"] = 501] = "amqp:not-implemented";
ConditionStatusMapper[ConditionStatusMapper["com.microsoft:entity-already-exists"] = 409] = "com.microsoft:entity-already-exists";
ConditionStatusMapper[ConditionStatusMapper["com.microsoft:session-lock-lost"] = 410] = "com.microsoft:session-lock-lost";
ConditionStatusMapper[ConditionStatusMapper["com.microsoft:no-matching-subscription"] = 500] = "com.microsoft:no-matching-subscription";
ConditionStatusMapper[ConditionStatusMapper["amqp:link:message-size-exceeded"] = 403] = "amqp:link:message-size-exceeded";
ConditionStatusMapper[ConditionStatusMapper["com.microsoft:server-busy"] = 503] = "com.microsoft:server-busy";
ConditionStatusMapper[ConditionStatusMapper["com.microsoft:argument-error"] = 400] = "com.microsoft:argument-error";
ConditionStatusMapper[ConditionStatusMapper["com.microsoft:argument-out-of-range"] = 400] = "com.microsoft:argument-out-of-range";
ConditionStatusMapper[ConditionStatusMapper["com.microsoft:store-lock-lost"] = 410] = "com.microsoft:store-lock-lost";
ConditionStatusMapper[ConditionStatusMapper["com.microsoft:session-cannot-be-locked"] = 410] = "com.microsoft:session-cannot-be-locked";
ConditionStatusMapper[ConditionStatusMapper["com.microsoft:partition-not-owned"] = 410] = "com.microsoft:partition-not-owned";
ConditionStatusMapper[ConditionStatusMapper["com.microsoft:entity-disabled"] = 400] = "com.microsoft:entity-disabled";
ConditionStatusMapper[ConditionStatusMapper["com.microsoft:publisher-revoked"] = 401] = "com.microsoft:publisher-revoked";
ConditionStatusMapper[ConditionStatusMapper["amqp:link:stolen"] = 410] = "amqp:link:stolen";
ConditionStatusMapper[ConditionStatusMapper["amqp:not-allowed"] = 400] = "amqp:not-allowed";
ConditionStatusMapper[ConditionStatusMapper["amqp:unauthorized-access"] = 401] = "amqp:unauthorized-access";
ConditionStatusMapper[ConditionStatusMapper["amqp:resource-limit-exceeded"] = 403] = "amqp:resource-limit-exceeded";
ConditionStatusMapper[ConditionStatusMapper["com.microsoft:message-lock-lost"] = 410] = "com.microsoft:message-lock-lost";
})(ConditionStatusMapper = exports.ConditionStatusMapper || (exports.ConditionStatusMapper = {}));
/**
* Maps the conditions to the Error names.
* @enum {ConditionErrorNameMapper}
*/
var ConditionErrorNameMapper;
(function (ConditionErrorNameMapper) {
/**
* Error is thrown when an internal server error occured. You may have found a bug?
*/
ConditionErrorNameMapper["amqp:internal-error"] = "InternalServerError";
/**
* Error for signaling general communication errors related to messaging operations.
*/
ConditionErrorNameMapper["amqp:not-found"] = "EventHubsCommunicationError";
/**
* Error is thrown when a feature is not implemented yet but the placeholder is present.
*/
ConditionErrorNameMapper["amqp:not-implemented"] = "NotImplementedError";
/**
* Error is thrown when an operation is attempted but is not allowed.
*/
ConditionErrorNameMapper["amqp:not-allowed"] = "InvalidOperationError";
/**
* Error is thrown the the Azure Event Hub 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
* been opened on a per-consumer group level.
*/
ConditionErrorNameMapper["amqp:resource-limit-exceeded"] = "QuotaExceededError";
/**
* Error is thrown when the connection parameters are wrong and the server refused the connection.
*/
ConditionErrorNameMapper["amqp:unauthorized-access"] = "UnauthorizedError";
/**
* Error is thrown when the service is unavailable. The operation should be retried.
*/
ConditionErrorNameMapper["com.microsoft:timeout"] = "ServiceUnavailableError";
/**
* Error is thrown when an argument has a value that is out of the admissible range.
*/
ConditionErrorNameMapper["com.microsoft:argument-out-of-range"] = "ArgumentOutOfRangeError";
/**
* Error is thrown when a condition that should have been met in order to execute an operation was not.
*/
ConditionErrorNameMapper["amqp:precondition-failed"] = "PreconditionFailedError";
/**
* Error is thrown when data could not be decoded.
*/
ConditionErrorNameMapper["amqp:decode-error"] = "DecodeError";
/**
* Error is thrown when an invalid field was passed in a frame body, and the operation could not proceed.
*/
ConditionErrorNameMapper["amqp:invalid-field"] = "InvalidFieldError";
/**
* Error is thrown when the client attempted to work with a server entity to which it
* has no access because another client is working with it.
*/
ConditionErrorNameMapper["amqp:resource-locked"] = "ResourceLockedError";
/**
* Error is thrown when a server entity the client is working with has been deleted.
*/
ConditionErrorNameMapper["amqp:resource-deleted"] = "ResourceDeletedError";
/**
* Error is thrown when the peer sent a frame that is not permitted in the current state.
*/
ConditionErrorNameMapper["amqp:illegal-state"] = "IllegalStateError";
/**
* Error is thrown when the peer cannot send a frame because the smallest encoding of
* the performative with the currently valid values would be too large to fit within
* a frame of the agreed maximum frame size.
*/
ConditionErrorNameMapper["amqp:frame-size-too-small"] = "FrameSizeTooSmallError";
/**
* Error is thrown when an operator intervened to detach for some reason.
*/
ConditionErrorNameMapper["amqp:link:detach-forced"] = "DetachForcedError";
/**
* Error is thrown when the peer sent more message transfers than currently allowed on the link.
*/
ConditionErrorNameMapper["amqp:link:transfer-limit-exceeded"] = "TransferLimitExceededError";
/**
* Error is thrown when the message sent is too large: the maximum size is 256Kb.
*/
ConditionErrorNameMapper["amqp:link:message-size-exceeded"] = "MessageTooLargeError";
/**
* Error is thrown when the address provided cannot be resolved to a terminus at the current container.
*/
ConditionErrorNameMapper["amqp:link:redirect"] = "LinkRedirectError";
/**
* Error is thrown when two or more instances connect to the same partition
* with different epoch values.
*/
ConditionErrorNameMapper["amqp:link:stolen"] = "ReceiverDisconnectedError";
/**
* Error is thrown when the peer violated incoming window for the session.
*/
ConditionErrorNameMapper["amqp:session:window-violation"] = "SessionWindowViolationError";
/**
* Error is thrown when input was received for a link that was detached with an error.
*/
ConditionErrorNameMapper["amqp:session:errant-link"] = "ErrantLinkError";
/**
* Error is thrown when an attach was received using a handle that is already in use for an attached link.
*/
ConditionErrorNameMapper["amqp:session:handle-in-use"] = "HanldeInUseError";
/**
* Error is thrown when a frame (other than attach) was received referencing a handle which is not
* currently in use of an attached link.
*/
ConditionErrorNameMapper["amqp:session:unattached-handle"] = "UnattachedHandleError";
/**
* Error is thrown when an operator intervened to close the connection for some reason.
*/
ConditionErrorNameMapper["amqp:connection:forced"] = "ConnectionForcedError";
/**
* Error is thrown when a valid frame header cannot be formed from the incoming byte stream.
*/
ConditionErrorNameMapper["amqp:connection:framing-error"] = "FramingError";
/**
* Error is thrown when the container is no longer available on the current connection.
*/
ConditionErrorNameMapper["amqp:connection:redirect"] = "ConnectionRedirectError";
/**
* Error is thrown when the server is busy. Callers should wait a while and retry the operation.
*/
ConditionErrorNameMapper["com.microsoft:server-busy"] = "ServerBusyError";
/**
* Error is thrown when an incorrect argument was received.
*/
ConditionErrorNameMapper["com.microsoft:argument-error"] = "ArgumentError";
})(ConditionErrorNameMapper = exports.ConditionErrorNameMapper || (exports.ConditionErrorNameMapper = {}));
/**
* Maps the conditions to the Error names.
* @enum {ErrorNameConditionMapper}
*/
var ErrorNameConditionMapper;
(function (ErrorNameConditionMapper) {
/**
* Error is thrown when an internal server error occured. You may have found a bug?
*/
ErrorNameConditionMapper["InternalServerError"] = "amqp:internal-error";
/**
* Error for signaling general communication errors related to messaging operations.
*/
ErrorNameConditionMapper["EventHubsCommunicationError"] = "amqp:not-found";
/**
* Error is thrown when a feature is not implemented yet but the placeholder is present.
*/
ErrorNameConditionMapper["NotImplementedError"] = "amqp:not-implemented";
/**
* Error is thrown when an operation is attempted but is not allowed.
*/
ErrorNameConditionMapper["InvalidOperationError"] = "amqp:not-allowed";
/**
* Error is thrown the the Azure Event Hub 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
* been opened on a per-consumer group level.
*/
ErrorNameConditionMapper["QuotaExceededError"] = "amqp:resource-limit-exceeded";
/**
* Error is thrown when the connection parameters are wrong and the server refused the connection.
*/
ErrorNameConditionMapper["UnauthorizedError"] = "amqp:unauthorized-access";
/**
* Error is thrown when the service is unavailable. The operation should be retried.
*/
ErrorNameConditionMapper["ServiceUnavailableError"] = "com.microsoft:timeout";
/**
* Error is thrown when an argument has a value that is out of the admissible range.
*/
ErrorNameConditionMapper["ArgumentOutOfRangeError"] = "com.microsoft:argument-out-of-range";
/**
* Error is thrown when a condition that should have been met in order to execute an operation was not.
*/
ErrorNameConditionMapper["PreconditionFailedError"] = "amqp:precondition-failed";
/**
* Error is thrown when data could not be decoded.
*/
ErrorNameConditionMapper["DecodeError"] = "amqp:decode-error";
/**
* Error is thrown when an invalid field was passed in a frame body, and the operation could not proceed.
*/
ErrorNameConditionMapper["InvalidFieldError"] = "amqp:invalid-field";
/**
* Error is thrown when the client attempted to work with a server entity to which it
* has no access because another client is working with it.
*/
ErrorNameConditionMapper["ResourceLockedError"] = "amqp:resource-locked";
/**
* Error is thrown when a server entity the client is working with has been deleted.
*/
ErrorNameConditionMapper["ResourceDeletedError"] = "amqp:resource-deleted";
/**
* Error is thrown when the peer sent a frame that is not permitted in the current state.
*/
ErrorNameConditionMapper["IllegalStateError"] = "amqp:illegal-state";
/**
* Error is thrown when the peer cannot send a frame because the smallest encoding of
* the performative with the currently valid values would be too large to fit within
* a frame of the agreed maximum frame size.
*/
ErrorNameConditionMapper["FrameSizeTooSmallError"] = "amqp:frame-size-too-small";
/**
* Error is thrown when an operator intervened to detach for some reason.
*/
ErrorNameConditionMapper["DetachForcedError"] = "amqp:link:detach-forced";
/**
* Error is thrown when the peer sent more message transfers than currently allowed on the link.
*/
ErrorNameConditionMapper["TransferLimitExceededError"] = "amqp:link:transfer-limit-exceeded";
/**
* Error is thrown when the message sent is too large: the maximum size is 256Kb.
*/
ErrorNameConditionMapper["MessageTooLargeError"] = "amqp:link:message-size-exceeded";
/**
* Error is thrown when the address provided cannot be resolved to a terminus at the current container.
*/
ErrorNameConditionMapper["LinkRedirectError"] = "amqp:link:redirect";
/**
* Error is thrown when two or more instances connect to the same partition
* with different epoch values.
*/
ErrorNameConditionMapper["ReceiverDisconnectedError"] = "amqp:link:stolen";
/**
* Error is thrown when the peer violated incoming window for the session.
*/
ErrorNameConditionMapper["SessionWindowViolationError"] = "amqp:session:window-violation";
/**
* Error is thrown when input was received for a link that was detached with an error.
*/
ErrorNameConditionMapper["ErrantLinkError"] = "amqp:session:errant-link";
/**
* Error is thrown when an attach was received using a handle that is already in use for an attached link.
*/
ErrorNameConditionMapper["HanldeInUseError"] = "amqp:session:handle-in-use";
/**
* Error is thrown when a frame (other than attach) was received referencing a handle which is not
* currently in use of an attached link.
*/
ErrorNameConditionMapper["UnattachedHandleError"] = "amqp:session:unattached-handle";
/**
* Error is thrown when an operator intervened to close the connection for some reason.
*/
ErrorNameConditionMapper["ConnectionForcedError"] = "amqp:connection:forced";
/**
* Error is thrown when a valid frame header cannot be formed from the incoming byte stream.
*/
ErrorNameConditionMapper["FramingError"] = "amqp:connection:framing-error";
/**
* Error is thrown when the container is no longer available on the current connection.
*/
ErrorNameConditionMapper["ConnectionRedirectError"] = "amqp:connection:redirect";
/**
* Error is thrown when the server is busy. Callers should wait a while and retry the operation.
*/
ErrorNameConditionMapper["ServerBusyError"] = "com.microsoft:server-busy";
/**
* Error is thrown when an incorrect argument was received.
*/
ErrorNameConditionMapper["ArgumentError"] = "com.microsoft:argument-error";
})(ErrorNameConditionMapper = exports.ErrorNameConditionMapper || (exports.ErrorNameConditionMapper = {}));
/**
* Describes the base class for an EventHub Error.
* @class {EventHubsError}
* @extends Error
*/
class EventHubsError extends Error {
/**
* @param {string} message The error message that provides more information about the error.
*/
constructor(message) {
super(message);
/**
* @property {string} name The error name. Default value: "EventHubsError".
*/
this.name = "EventHubsError";
/**
* @property {boolean} translated Has the error been translated. Default: true.
*/
this.translated = true;
/**
*
* @param {boolean} retryable Describes whether the error is retryable. Default: false.
*/
this.retryable = false;
}
}
exports.EventHubsError = EventHubsError;
/**
* Determines whether the given error object is like an AmqpError object.
* @param err The AmqpError object
*/
function isAmqpError(err) {
if (!err || typeof err !== "object") {
throw new Error("err is a required parameter and must be of type 'object'.");
}
let result = 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.
*
* @param {AmqpError} err The amqp error that was received.
* @returns {EventHubsError} EventHubsError object.
*/
function translate(err) {
if (err.translated) { // already translated
return err;
}
else if (isAmqpError(err)) { // translate
const condition = err.condition;
const description = err.description;
const error = new EventHubsError(description);
error.condition = condition;
if (condition) {
if (condition === "com.microsoft:precondition-failed")
error.name = "PreconditionFailedError";
else
error.name = ConditionErrorNameMapper[condition] || "EventHubsError";
}
if (description &&
(description.includes("status-code: 404") ||
description.match(/The messaging entity .* could not be found.*/i) !== null)) {
error.name = "MessagingEntityNotFoundError";
}
if (error.name === "InternalServerError"
|| error.name === "ServerBusyError"
|| error.name === "ServiceUnavailableError") {
error.retryable = true;
}
return error;
}
else {
// Translate a generic error into EventHubsError.
const error = new EventHubsError(err.message);
return error;
}
}
exports.translate = translate;

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

@ -0,0 +1,109 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
const Constants = require("./util/constants");
exports.messageProperties = [
"message_id", "reply_to", "to", "correlation_id", "content_type", "absolute_expiry_time",
"group_id", "group_sequence", "reply_to_group_id", "content_encoding", "creation_time"
];
/**
* Describes the methods on the EventData interface.
* @module EventData
*/
var EventData;
(function (EventData) {
/**
* Converts the AMQP message to an EventData.
* @param {AmqpMessage} msg The AMQP message that needs to be converted to EventData.
*/
function fromAmqpMessage(msg) {
// TODO: Look at how other sdks are encoding their payloads and copy them. This will ensure consistency across all the sdks.
const data = {
body: msg.body,
_raw_amqp_mesage: msg
};
if (msg.message_annotations) {
data.annotations = msg.message_annotations;
if (msg.message_annotations[Constants.partitionKey])
data.partitionKey = msg.message_annotations[Constants.partitionKey];
if (msg.message_annotations[Constants.sequenceNumber])
data.sequenceNumber = msg.message_annotations[Constants.sequenceNumber];
if (msg.message_annotations[Constants.enqueuedTime])
data.enqueuedTimeUtc = new Date(msg.message_annotations[Constants.enqueuedTime]);
if (msg.message_annotations[Constants.offset])
data.offset = msg.message_annotations[Constants.offset];
}
// Since rhea expects message properties as top level properties we will look for them and unflatten them inside properties.
for (const prop of exports.messageProperties) {
if (msg[prop]) {
if (!data.properties) {
data.properties = {};
}
data.properties[prop] = msg[prop];
}
}
if (msg.application_properties) {
data.applicationProperties = msg.application_properties;
}
if (msg.delivery_annotations) {
data.lastEnqueuedOffset = msg.delivery_annotations.last_enqueued_offset;
data.lastSequenceNumber = msg.delivery_annotations.last_enqueued_sequence_number;
data.lastEnqueuedTime = new Date(msg.delivery_annotations.last_enqueued_time_utc);
data.retrievalTime = new Date(msg.delivery_annotations.runtime_info_retrieval_time_utc);
}
return data;
}
EventData.fromAmqpMessage = fromAmqpMessage;
/**
* Converts an EventData object to an AMQP message.
* @param {EventData} data The EventData object that needs to be converted to an AMQP message.
*/
function toAmqpMessage(data) {
const msg = {
body: data.body,
};
// As per the AMQP 1.0 spec If the message-annotations or delivery-annotations section is omitted,
// it is equivalent to a message-annotations section containing anempty map of annotations.
msg.message_annotations = {};
msg.delivery_annotations = {};
if (data.annotations) {
msg.message_annotations = data.annotations;
}
if (data.properties) {
// Set amqp message properties as top level properties, since rhea sends them as top level properties.
for (const prop in data.properties) {
msg[prop] = data.properties[prop];
}
}
if (data.applicationProperties) {
msg.application_properties = data.applicationProperties;
}
if (data.partitionKey) {
msg.message_annotations[Constants.partitionKey] = data.partitionKey;
}
if (data.sequenceNumber != undefined) {
msg.message_annotations[Constants.sequenceNumber] = data.sequenceNumber;
}
if (data.enqueuedTimeUtc) {
msg.message_annotations[Constants.enqueuedTime] = data.enqueuedTimeUtc.getTime();
}
if (data.offset != undefined) {
msg.message_annotations[Constants.offset] = data.offset;
}
if (data.lastEnqueuedOffset != undefined) {
msg.delivery_annotations.last_enqueued_offset = data.lastEnqueuedOffset;
}
if (data.lastSequenceNumber != undefined) {
msg.delivery_annotations.last_enqueued_sequence_number = data.lastSequenceNumber;
}
if (data.lastEnqueuedTime) {
msg.delivery_annotations.last_enqueued_time_utc = data.lastEnqueuedTime.getTime();
}
if (data.retrievalTime) {
msg.delivery_annotations.runtime_info_retrieval_time_utc = data.retrievalTime.getTime();
}
return msg;
}
EventData.toAmqpMessage = toAmqpMessage;
})(EventData = exports.EventData || (exports.EventData = {}));

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

@ -0,0 +1,295 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const debugModule = require("debug");
const rhea_promise_1 = require("./rhea-promise");
const ms_rest_azure_1 = require("ms-rest-azure");
const _1 = require(".");
const rpc = require("./rpc");
const connectionContext_1 = require("./connectionContext");
const aad_1 = require("./auth/aad");
const eventHubSender_1 = require("./eventHubSender");
const streamingReceiver_1 = require("./streamingReceiver");
const batchingReceiver_1 = require("./batchingReceiver");
const debug = debugModule("azure:event-hubs:client");
/**
* @class EventHubClient
* Describes the EventHub client.
*/
class EventHubClient {
/**
* Instantiate a client pointing to the Event Hub given by this configuration.
*
* @constructor
* @param {ConnectionConfig} config - The connection configuration to create the EventHub Client.
* @param {TokenProvider} [tokenProvider] - The token provider that provides the token for authentication.
* Default value: SasTokenProvider.
*/
constructor(config, tokenProvider) {
this._context = connectionContext_1.ConnectionContext.create(config, tokenProvider);
}
/**
* Closes the AMQP connection to the Event Hub for this client,
* returning a promise that will be resolved when disconnection is completed.
* @method close
* @returns {Promise<any>}
*/
close() {
return __awaiter(this, void 0, void 0, function* () {
try {
if (this._context.connection) {
// Close all the senders.
for (const sender of Object.values(this._context.senders)) {
yield sender.close();
}
// Close all the receivers.
for (const receiver of Object.values(this._context.receivers)) {
yield receiver.close();
}
// Close the cbs session;
yield this._context.cbsSession.close();
// Close the management session
yield this._context.managementSession.close();
yield rhea_promise_1.closeConnection(this._context.connection);
debug("Closed the amqp connection '%s' on the client.", this._context.connectionId);
this._context.connection = undefined;
}
}
catch (err) {
const msg = `An error occurred while closing the connection "${this._context.connectionId}": ${JSON.stringify(err)}`;
debug(msg);
throw new Error(msg);
}
});
}
/**
* Sends the given message to the EventHub.
*
* @method send
* @param {any} data Message to send. Will be sent as UTF8-encoded JSON string.
* @param {string|number} [partitionId] Partition ID to which the event data needs to be sent. This should only be specified
* if you intend to send the event to a specific partition. When not specified EventHub will store the messages in a round-robin
* fashion amongst the different partitions in the EventHub.
*
* @returns {Promise<Delivery>} Promise<rheaPromise.Delivery>
*/
send(data, partitionId) {
return __awaiter(this, void 0, void 0, function* () {
const sender = eventHubSender_1.EventHubSender.create(this._context, partitionId);
return yield sender.send(data);
});
}
/**
* Send a batch of EventData to the EventHub. The "message_annotations", "application_properties" and "properties"
* of the first message will be set as that of the envelope (batch message).
*
* @method sendBatch
* @param {Array<EventData>} datas An array of EventData objects to be sent in a Batch message.
* @param {string|number} [partitionId] Partition ID to which the event data needs to be sent. This should only be specified
* if you intend to send the event to a specific partition. When not specified EventHub will store the messages in a round-robin
* fashion amongst the different partitions in the EventHub.
*
* @return {Promise<rheaPromise.Delivery>} Promise<rheaPromise.Delivery>
*/
sendBatch(datas, partitionId) {
return __awaiter(this, void 0, void 0, function* () {
const sender = eventHubSender_1.EventHubSender.create(this._context, partitionId);
return yield sender.sendBatch(datas);
});
}
/**
* Starts the receiver by establishing an AMQP session and an AMQP receiver link on the session. Messages will be passed to
* the provided onMessage handler and error will be passes to the provided onError handler.
*
* @param {string|number} partitionId Partition ID from which to receive.
* @param {OnMessage} onMessage The message handler to receive event data objects.
* @param {OnError} onError The error handler to receive an error that occurs
* while receiving messages.
* @param {ReceiveOptions} [options] Options for how you'd like to connect.
* @param {string} [name] The name of the receiver. If not provided
* then we will set a GUID by default.
* @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.
*
* @returns {ReceiveHandler} ReceiveHandler - An object that provides a mechanism to stop receiving more messages.
*/
receiveOnMessage(partitionId, onMessage, onError, options) {
if (!partitionId || (partitionId && typeof partitionId !== "string" && typeof partitionId !== "number")) {
throw new Error("'partitionId' is a required parameter and must be of type: 'string' | 'number'.");
}
const sReceiver = streamingReceiver_1.StreamingReceiver.create(this._context, partitionId, options);
this._context.receivers[sReceiver.name] = sReceiver;
sReceiver.receiveOnMessage(onMessage, onError);
return new streamingReceiver_1.ReceiveHandler(sReceiver);
}
/**
* Receives a batch of EventData objects from an EventHub partition for a given count and a given max wait time in seconds, whichever
* happens first. This method can be used directly after creating the receiver object and **MUST NOT** be used along with the `start()` method.
*
* @param {string|number} partitionId Partition ID from which to receive.
* @param {number} maxMessageCount The maximum message count. Must be a value greater than 0.
* @param {number} [maxWaitTimeInSeconds] The maximum wait time in seconds for which the Receiver should wait
* to receiver the said amount of messages. If not provided, it defaults to 60 seconds.
* @param {ReceiveOptions} [options] Options for how you'd like to connect.
* @param {string} [name] The name of the receiver. If not provided
* then we will set a GUID by default.
* @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.
*
* @returns {Promise<EventData[]>} A promise that resolves with an array of EventData objects.
*/
receiveBatch(partitionId, maxMessageCount, maxWaitTimeInSeconds, options) {
return __awaiter(this, void 0, void 0, function* () {
if (!partitionId || (partitionId && typeof partitionId !== "string" && typeof partitionId !== "number")) {
throw new Error("'partitionId' is a required parameter and must be of type: 'string' | 'number'.");
}
const bReceiver = batchingReceiver_1.BatchingReceiver.create(this._context, partitionId, options);
let error;
let result = [];
try {
result = yield bReceiver.receive(maxMessageCount, maxWaitTimeInSeconds);
}
catch (err) {
error = err;
debug("[%s] Receiver '%s', an error occurred while receiving %d messages for %d max time:\n %O", this._context.connectionId, bReceiver.name, maxMessageCount, maxWaitTimeInSeconds, err);
}
try {
yield bReceiver.close();
}
catch (err) {
// do nothing about it.
}
if (error) {
throw error;
}
return result;
});
}
/**
* Provides the eventhub runtime information.
* @method getHubRuntimeInformation
* @returns {Promise<EventHubRuntimeInformation>}
*/
getHubRuntimeInformation() {
return __awaiter(this, void 0, void 0, function* () {
try {
yield rpc.open(this._context);
return yield this._context.managementSession.getHubRuntimeInformation(this._context.connection);
}
catch (err) {
debug("An error occurred while getting the hub runtime information: %O", err);
throw err;
}
});
}
/**
* Provides an array of partitionIds.
* @method getPartitionIds
* @returns {Promise<Array<string>>}
*/
getPartitionIds() {
return __awaiter(this, void 0, void 0, function* () {
try {
const runtimeInfo = yield this.getHubRuntimeInformation();
return runtimeInfo.partitionIds;
}
catch (err) {
debug("An error occurred while getting the partition ids: %O", err);
throw err;
}
});
}
/**
* Provides information about the specified partition.
* @method getPartitionInformation
* @param {(string|number)} partitionId Partition ID for which partition information is required.
*/
getPartitionInformation(partitionId) {
return __awaiter(this, void 0, void 0, function* () {
if (!partitionId || (partitionId && typeof partitionId !== "string" && typeof partitionId !== "number")) {
throw new Error("'partitionId' is a required parameter and must be of type: 'string' | 'number'.");
}
try {
yield rpc.open(this._context);
return yield this._context.managementSession.getPartitionInformation(this._context.connection, partitionId);
}
catch (err) {
debug("An error occurred while getting the partition information: %O", err);
throw err;
}
});
}
/**
* Creates an EventHub Client from connection string.
* @method createFromConnectionString
* @param {string} connectionString - Connection string of the form 'Endpoint=sb://my-servicebus-namespace.servicebus.windows.net/;SharedAccessKeyName=my-SA-name;SharedAccessKey=my-SA-key'
* @param {string} [path] - EventHub path of the form 'my-event-hub-name'
* @param {TokenProvider} [tokenProvider] - An instance of the token provider that provides the token for authentication. Default value: SasTokenProvider.
* @returns {EventHubClient} - An instance of the eventhub client.
*/
static createFromConnectionString(connectionString, path, tokenProvider) {
if (!connectionString || (connectionString && typeof connectionString !== "string")) {
throw new Error("'connectionString' is a required parameter and must be of type: 'string'.");
}
const config = _1.ConnectionConfig.create(connectionString, path);
if (!config.entityPath) {
throw new Error(`Either the connectionString must have "EntityPath=<path-to-entity>" or you must provide "path", while creating the client`);
}
return new EventHubClient(config, tokenProvider);
}
/**
* Creates an EventHub Client from AADTokenCredentials.
* @method
* @param {string} host - Fully qualified domain name for Event Hubs. Most likely, {yournamespace}.servicebus.windows.net
* @param {string} entityPath - EventHub path of the form 'my-event-hub-name'
* @param {TokenCredentials} credentials - The AAD Token credentials. It can be one of the following: ApplicationTokenCredentials | UserTokenCredentials | DeviceTokenCredentials | MSITokenCredentials.
*/
static createFromAadTokenCredentials(host, entityPath, credentials) {
if (!host || (host && typeof host !== "string")) {
throw new Error("'host' is a required parameter and must be of type: 'string'.");
}
if (!entityPath || (entityPath && typeof entityPath !== "string")) {
throw new Error("'entityPath' is a required parameter and must be of type: 'string'.");
}
if (!credentials ||
!(credentials instanceof ms_rest_azure_1.ApplicationTokenCredentials ||
credentials instanceof ms_rest_azure_1.UserTokenCredentials ||
credentials instanceof ms_rest_azure_1.DeviceTokenCredentials ||
credentials instanceof ms_rest_azure_1.MSITokenCredentials)) {
throw new Error("'credentials' is a required parameter and must be an instance of ApplicationTokenCredentials | UserTokenCredentials | DeviceTokenCredentials | MSITokenCredentials.");
}
if (!host.endsWith("/"))
host += "/";
const connectionString = `Endpoint=sb://${host};SharedAccessKeyName=defaultKeyName;SharedAccessKey=defaultKeyValue`;
const tokenProvider = new aad_1.AadTokenProvider(credentials);
return EventHubClient.createFromConnectionString(connectionString, entityPath, tokenProvider);
}
}
exports.EventHubClient = EventHubClient;

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

@ -0,0 +1,257 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const rhea = require("rhea");
const debugModule = require("debug");
const uuid = require("uuid/v4");
const rheaPromise = require("./rhea-promise");
const rpc = require("./rpc");
const errors_1 = require("./errors");
const Constants = require("./util/constants");
const _1 = require(".");
const utils_1 = require("./util/utils");
const debug = debugModule("azure:event-hubs:receiver");
/**
* Describes the EventHubReceiver that will receive event data from EventHub.
* @class EventHubReceiver
*/
class EventHubReceiver {
/**
* Instantiate a new receiver from the AMQP `Receiver`. Used by `EventHubClient`.
*
* @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.
*/
constructor(context, partitionId, options) {
/**
* @property {number} [prefetchCount] The number of messages that the receiver can fetch/receive initially. Defaults to 1000.
*/
this.prefetchCount = Constants.defaultPrefetchCount;
/**
* @property {boolean} receiverRuntimeMetricEnabled Indicates whether receiver runtime metric is enabled. Default: false.
*/
this.receiverRuntimeMetricEnabled = false;
if (!options)
options = {};
this._context = context;
this.name = options.name || uuid();
this.partitionId = partitionId;
this.consumerGroup = options.consumerGroup ? options.consumerGroup : Constants.defaultConsumerGroup;
this.address = `${this._context.config.entityPath}/ConsumerGroups/${this.consumerGroup}/Partitions/${this.partitionId}`;
this.audience = `${this._context.config.endpoint}${this.address}`;
this.prefetchCount = options.prefetchCount !== undefined && options.prefetchCount !== null ? options.prefetchCount : Constants.defaultPrefetchCount;
this.epoch = options.epoch;
this.identifier = options.identifier;
this.options = options;
this.receiverRuntimeMetricEnabled = options.enableReceiverRuntimeMetric || false;
this.runtimeInfo = {
partitionId: `${partitionId}`
};
this._onAmqpMessage = (context) => {
const evData = _1.EventData.fromAmqpMessage(context.message);
if (this.receiverRuntimeMetricEnabled && evData) {
this.runtimeInfo.lastSequenceNumber = evData.lastSequenceNumber;
this.runtimeInfo.lastEnqueuedTimeUtc = evData.lastEnqueuedTime;
this.runtimeInfo.lastEnqueuedOffset = evData.lastEnqueuedOffset;
this.runtimeInfo.retrievalTime = evData.retrievalTime;
}
this._onMessage(evData);
};
this._onAmqpError = (context) => {
const ehError = errors_1.translate(context.receiver.error);
// TODO: Should we retry before calling user's error method?
debug("[%s] An error occurred for Receiver '%s': %O.", this._context.connectionId, this.name, ehError);
this._onError(ehError);
};
}
/**
* Closes the underlying AMQP receiver.
* @param {boolean} [preserveInContext] Should the receiver be preserved in context. Default value false.
*/
close() {
return __awaiter(this, void 0, void 0, function* () {
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.
yield rheaPromise.closeReceiver(this._receiver);
// 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);
debug("[%s] Receiver '%s', has been closed.", this._context.connectionId, this.name);
}
catch (err) {
debug("An error occurred while closing the receiver %s %O", this.name, errors_1.translate(err));
}
}
});
}
/**
* Creates a new AMQP receiver under a new AMQP session.
* @returns {Promise<void>}
*/
_init(onAmqpMessage, onAmqpError) {
return __awaiter(this, void 0, void 0, function* () {
try {
// Acquire the lock and establish an amqp connection if it does not exist.
if (!this._context.connection) {
debug("[%s] EH Receiver '%s' establishing AMQP connection.", this._context.connectionId, this.name);
yield utils_1.defaultLock.acquire(this._context.connectionLock, () => { return rpc.open(this._context); });
}
if (!this._isOpen()) {
yield this._negotiateClaim();
if (!onAmqpMessage) {
onAmqpMessage = this._onAmqpMessage;
}
if (!onAmqpError) {
onAmqpError = this._onAmqpError;
}
this._session = yield rheaPromise.createSession(this._context.connection);
debug("[%s] Trying to create receiver '%s'...", this._context.connectionId, this.name);
const rcvrOptions = this._createReceiverOptions();
this._receiver = yield rheaPromise.createReceiverWithHandlers(this._session, onAmqpMessage, onAmqpError, 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);
// It is possible for someone to close the receiver and then start it again.
// Thus make sure that the receiver is present in the client cache.
if (!this._context.receivers[this.name])
this._context.receivers[this.name] = this;
yield this._ensureTokenRenewal();
}
}
catch (err) {
err = errors_1.translate(err);
debug("[%s] An error occured while creating the receiver '%s': %O", this._context.connectionId, this.name, err);
throw err;
}
});
}
/**
* Determines whether the AMQP receiver link is open. If open then returns true else returns false.
* @protected
*
* @return {boolean} boolean
*/
_isOpen() {
let result = 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
*/
_createReceiverOptions() {
const rcvrOptions = {
name: this.name,
autoaccept: true,
source: {
address: this.address
},
credit_window: this.prefetchCount,
};
if (this.epoch !== undefined && this.epoch !== null) {
if (!rcvrOptions.properties)
rcvrOptions.properties = {};
rcvrOptions.properties[Constants.attachEpoch] = rhea.types.wrap_long(this.epoch);
}
if (this.identifier) {
if (!rcvrOptions.properties)
rcvrOptions.properties = {};
rcvrOptions.properties[Constants.receiverIdentifierName] = this.identifier;
}
if (this.receiverRuntimeMetricEnabled) {
rcvrOptions.desired_capabilities = Constants.enableReceiverRuntimeMetricName;
}
if (this.options && this.options.eventPosition) {
// Set filter on the receiver if event position is specified.
const filterClause = this.options.eventPosition.getExpression();
if (filterClause) {
rcvrOptions.source.filter = {
"apache.org:selector-filter:string": rhea.types.wrap_described(filterClause, 0x468C00000004)
};
}
}
return rcvrOptions;
}
/**
* Negotiates the cbs claim for the EventHubReceiver.
* @private
* @param {boolean} [setTokenRenewal] Set the token renewal timer. Default false.
* @return {Promise<void>} Promise<void>
*/
_negotiateClaim(setTokenRenewal) {
return __awaiter(this, void 0, void 0, function* () {
// Acquire the lock and establish a cbs session if it does not exist on the connection. Although node.js
// is single threaded, we need a locking mechanism to ensure that a race condition does not happen while
// creating a shared resource (in this case the cbs session, since we want to have exactly 1 cbs session
// per connection).
debug("Acquiring lock: '%s' for creating the cbs session while creating the receiver: ${this.name}.", this._context.connectionId, this._context.cbsSession.cbsLock, this.name);
// Acquire the lock and establish a cbs session if it does not exist on the connection.
yield utils_1.defaultLock.acquire(this._context.cbsSession.cbsLock, () => { return this._context.cbsSession.init(this._context.connection); });
const tokenObject = yield this._context.tokenProvider.getToken(this.audience);
debug("[%s] EH Receiver '%s': calling negotiateClaim for audience '%s'.", this._context.connectionId, this.audience);
// Acquire the lock to negotiate the CBS claim.
debug("[%s] Acquiring lock: '%s' for cbs auth for receiver: '%s'.", this._context.connectionId, this._context.negotiateClaimLock, this.name);
yield utils_1.defaultLock.acquire(this._context.negotiateClaimLock, () => {
return this._context.cbsSession.negotiateClaim(this.audience, this._context.connection, tokenObject);
});
debug("[%s] Negotiated claim for receiver '%s' with with partition '%s'", this._context.connectionId, this.name, this.partitionId);
if (setTokenRenewal) {
yield this._ensureTokenRenewal();
}
});
}
/**
* Ensures that the token is renewed within the predefined renewal margin.
* @private
* @return {Promise<void>} Promise<void>
*/
_ensureTokenRenewal() {
return __awaiter(this, void 0, void 0, function* () {
const tokenValidTimeInSeconds = this._context.tokenProvider.tokenValidTimeInSeconds;
const tokenRenewalMarginInSeconds = this._context.tokenProvider.tokenRenewalMarginInSeconds;
const nextRenewalTimeout = (tokenValidTimeInSeconds - tokenRenewalMarginInSeconds) * 1000;
this._tokenRenewalTimer = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
try {
yield this._negotiateClaim(true);
}
catch (err) {
// TODO: May be add some retries over here before emitting the error.
debug("[%s] Receiver '%s', an error occurred while renewing the token: %O", this._context.connectionId, this.name, errors_1.translate(err));
}
}), nextRenewalTimeout);
debug("[%s]Receiver '%s', has next token renewal in %d seconds @(%s).", this._context.connectionId, this.name, nextRenewalTimeout / 1000, new Date(Date.now() + nextRenewalTimeout).toString());
});
}
}
exports.EventHubReceiver = EventHubReceiver;

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

@ -0,0 +1,365 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const rhea = require("rhea");
const debugModule = require("debug");
const uuid = require("uuid/v4");
const errors_1 = require("./errors");
const rpc = require("./rpc");
const rheaPromise = require("./rhea-promise");
const eventData_1 = require("./eventData");
const utils_1 = require("./util/utils");
const retry_1 = require("./retry");
const debug = debugModule("azure:event-hubs:sender");
/**
* Instantiates a new sender from the AMQP `Sender`. Used by `EventHubClient`.
*
* @param {any} session - The amqp session on which the amqp sender link was created.
* @param {any} sender - The amqp sender link.
* @constructor
*/
class EventHubSender {
/**
* Creates a new EventHubSender instance.
* @constructor
* @param {EventHubClient} client The EventHub client.
* @param {string|number} [partitionId] The EventHub partition id to which the sender
* wants to send the event data.
*/
constructor(context, partitionId, name) {
this.senderLock = `sender-${uuid()}`;
this._context = context;
this.name = name || uuid();
this.address = this._context.config.entityPath;
this.partitionId = partitionId;
if (this.partitionId !== null && this.partitionId !== undefined) {
this.address += `/Partitions/${this.partitionId}`;
}
this.audience = `${this._context.config.endpoint}${this.address}`;
}
/**
* Sends the given message, with the given options on this link
*
* @method send
* @param {any} data Message to send. Will be sent as UTF8-encoded JSON string.
* @returns {Promise<rheaPromise.Delivery>} Promise<rheaPromise.Delivery>
*/
send(data) {
return __awaiter(this, void 0, void 0, function* () {
try {
if (!data || (data && typeof data !== "object")) {
throw new Error("data is required and it must be of type object.");
}
if (!this._isOpen()) {
debug("Acquiring lock %s for initializing the session, sender and " +
"possibly the connection.", this.senderLock);
yield utils_1.defaultLock.acquire(this.senderLock, () => { return this._init(); });
}
const message = eventData_1.EventData.toAmqpMessage(data);
return yield this._trySend(message);
}
catch (err) {
debug("An error occurred while sending the message %O", err);
throw err;
}
});
}
/**
* Send a batch of EventData to the EventHub. The "message_annotations",
* "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>
*/
sendBatch(datas) {
return __awaiter(this, void 0, void 0, function* () {
try {
if (!datas || (datas && !Array.isArray(datas))) {
throw new Error("data is required and it must be an Array.");
}
if (!this._isOpen()) {
debug("Acquiring lock %s for initializing the session, sender and " +
"possibly the connection.", this.senderLock);
yield utils_1.defaultLock.acquire(this.senderLock, () => { return this._init(); });
}
debug("[%s] Sender '%s', trying to send EventData[]: %O", this._context.connectionId, this.name, datas);
const messages = [];
// Convert EventData to AmqpMessage.
for (let i = 0; i < datas.length; i++) {
const message = eventData_1.EventData.toAmqpMessage(datas[i]);
messages[i] = message;
}
// Encode every amqp message and then convert every encoded message to amqp data section
const batchMessage = {
body: rhea.message.data_sections(messages.map(rhea.message.encode))
};
// Set message_annotations, application_properties and properties of the first message as
// that of the envelope (batch message).
if (messages[0].message_annotations) {
batchMessage.message_annotations = messages[0].message_annotations;
}
if (messages[0].application_properties) {
batchMessage.application_properties = messages[0].application_properties;
}
for (const prop of eventData_1.messageProperties) {
if (messages[0][prop]) {
batchMessage[prop] = messages[0][prop];
}
}
// Finally encode the envelope (batch message).
const encodedBatchMessage = rhea.message.encode(batchMessage);
debug("[%s]Sender '%s', sending encoded batch message.", this._context.connectionId, this.name, encodedBatchMessage);
return yield this._trySend(encodedBatchMessage, undefined, 0x80013700);
}
catch (err) {
debug("An error occurred while sending the batch message %O", err);
throw err;
}
});
}
/**
* "Unlink" this sender, closing the link and resolving when that operation is complete.
* Leaves the underlying connection open.
* @method close
* @return {Promise<void>} Promise<void>
*/
close() {
return __awaiter(this, void 0, void 0, function* () {
if (this._sender) {
try {
yield rheaPromise.closeSender(this._sender);
delete this._context.senders[this.name];
debug("[%s] Deleted the sender '%s' from the client cache.", this._context.connectionId, this.name);
this._sender = undefined;
this._session = undefined;
clearTimeout(this._tokenRenewalTimer);
debug("[%s]Sender '%s' closed.", this._context.connectionId, this.name);
}
catch (err) {
debug("An error occurred while closing the sender %O", err);
throw err;
}
}
});
}
_createSenderOptions() {
const options = {
name: this.name,
target: {
address: this.address
}
};
debug("Creating sender with options: %O", options);
return options;
}
/**
* Tries to send the message to EventHub if there is enough credit to send them
* and the circular buffer has available space to settle the message after sending them.
*
* We have implemented a synchronous send over here in the sense that we shall be waiting
* 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>
*/
_trySend(message, tag, format) {
const sendEventPromise = new Promise((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()) {
debug("[%s] Sender '%s', sending message: %O", this._context.connectionId, this.name, message);
let onRejected;
let onReleased;
let onModified;
let onAccepted;
const removeListeners = () => {
this._sender.removeListener("rejected", onRejected);
this._sender.removeListener("accepted", onAccepted);
this._sender.removeListener("released", onReleased);
this._sender.removeListener("modified", onModified);
};
onAccepted = (context) => {
// 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.
removeListeners();
debug("[%s] Sender '%s', got event accepted.", this._context.connectionId, this.name);
resolve(context.delivery);
};
onRejected = (context) => {
removeListeners();
debug("[%s] Sender '%s', got event rejected.", this._context.connectionId, this.name);
reject(errors_1.translate(context.delivery.remote_state.error));
};
onReleased = (context) => {
removeListeners();
debug("[%s] Sender '%s', got event released.", this._context.connectionId, this.name);
let err;
if (context.delivery.remote_state.error) {
err = errors_1.translate(context.delivery.remote_state.error);
}
else {
err = new Error(`[${this._context.connectionId}] Sender '${this.name}', ` +
`received a release disposition. Hence we are rejecting the promise.`);
}
reject(err);
};
onModified = (context) => {
removeListeners();
debug("[%s] Sender '%s', got event modified.", this._context.connectionId, this.name);
let err;
if (context.delivery.remote_state.error) {
err = errors_1.translate(context.delivery.remote_state.error);
}
else {
err = new Error(`[${this._context.connectionId}] Sender "${this.name}", ` +
`received a modified disposition. Hence we are rejecting the promise.`);
}
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);
debug("[%s] Sender '%s', sent message with delivery id: %d", this._context.connectionId, this.name, delivery.id);
}
else {
const msg = `[${this._context.connectionId}] Sender "${this.name}", ` +
`cannot send the message right now. Please try later.`;
debug(msg);
reject(new Error(msg));
}
});
return retry_1.retry(() => sendEventPromise);
}
/**
* Determines whether the AMQP sender link is open. If open then returns true else returns false.
* @private
*
* @return {boolean} boolean
*/
_isOpen() {
let result = false;
if (this._session && this._sender) {
if (this._sender.is_open && this._sender.is_open()) {
result = true;
}
}
return result;
}
/**
* Initializes the sender session on the connection.
* @returns {Promise<void>}
*/
_init() {
return __awaiter(this, void 0, void 0, function* () {
try {
// Acquire the lock and establish an amqp connection if it does not exist.
if (!this._context.connection) {
debug("[%s] EH Sender '%s' establishing an AMQP connection.", this._context.connectionId, this.name);
yield utils_1.defaultLock.acquire(this._context.connectionLock, () => { return rpc.open(this._context); });
}
if (!this._session && !this._sender) {
yield this._negotiateClaim();
const onAmqpError = (context) => {
const senderError = errors_1.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 = yield rheaPromise.createSession(this._context.connection);
debug("[%s] Trying to create sender '%s'...", this._context.connectionId, this.name);
const options = this._createSenderOptions();
this._sender = yield rheaPromise.createSenderWithHandlers(this._session, onAmqpError, 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", this._context.connectionId, this.name, options);
// It is possible for someone to close the sender and then start it again.
// Thus make sure that the sender is present in the client cache.
if (!this._context.senders[this.address])
this._context.senders[this.address] = this;
yield this._ensureTokenRenewal();
}
}
catch (err) {
err = errors_1.translate(err);
debug("[%s] An error occurred while creating the sender %s", this._context.connectionId, this.name, err);
throw err;
}
});
}
/**
* Negotiates the cbs claim for the EventHub Sender.
* @private
* @param {boolean} [setTokenRenewal] Set the token renewal timer. Default false.
* @return {Promise<void>} Promise<void>
*/
_negotiateClaim(setTokenRenewal) {
return __awaiter(this, void 0, void 0, function* () {
// Acquire the lock and establish a cbs session if it does not exist on the connection.
// Although node.js is single threaded, we need a locking mechanism to ensure that a
// race condition does not happen while creating a shared resource (in this case the
// cbs session, since we want to have exactly 1 cbs session per connection).
debug("[%s] Acquiring lock: '%s' for creating the cbs session while creating the sender: '%s'.", this._context.connectionId, this._context.cbsSession.cbsLock, this.name);
yield utils_1.defaultLock.acquire(this._context.cbsSession.cbsLock, () => { return this._context.cbsSession.init(this._context.connection); });
const tokenObject = yield this._context.tokenProvider.getToken(this.audience);
debug("[%s] EH Sender: calling negotiateClaim for audience '%s'.", this._context.connectionId, this.audience);
// Acquire the lock to negotiate the CBS claim.
debug("[%s] Acquiring lock: '%s' for cbs auth for sender: '%s'.", this._context.connectionId, this._context.negotiateClaimLock, this.name);
yield utils_1.defaultLock.acquire(this._context.negotiateClaimLock, () => {
return this._context.cbsSession.negotiateClaim(this.audience, this._context.connection, tokenObject);
});
debug("[%s] Negotiated claim for sender '%s' with with partition: %s", this._context.connectionId, this.partitionId);
if (setTokenRenewal) {
yield this._ensureTokenRenewal();
}
});
}
/**
* Ensures that the token is renewed within the predefined renewal margin.
* @private
* @returns {void}
*/
_ensureTokenRenewal() {
return __awaiter(this, void 0, void 0, function* () {
const tokenValidTimeInSeconds = this._context.tokenProvider.tokenValidTimeInSeconds;
const tokenRenewalMarginInSeconds = this._context.tokenProvider.tokenRenewalMarginInSeconds;
const nextRenewalTimeout = (tokenValidTimeInSeconds - tokenRenewalMarginInSeconds) * 1000;
this._tokenRenewalTimer = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
try {
yield this._negotiateClaim(true);
}
catch (err) {
// TODO: May be add some retries over here before emitting the error.
debug("[%s] Sender '%s', an error occurred while renewing the token: %O", this._context.connectionId, this.name, err);
}
}), nextRenewalTimeout);
debug("[%s]Sender '%s', has next token renewal in %d seconds @(%s).", nextRenewalTimeout / 1000, new Date(Date.now() + nextRenewalTimeout).toString());
});
}
/**
* Creates a new sender to the given event hub, and optionally to a given partition if it is
* not present in the context or returns the one present in the context.
* @static
* @param {(string|number)} [partitionId] Partition ID to which it will send event data.
* @returns {Promise<EventHubSender>}
*/
static create(context, partitionId) {
if (partitionId && typeof partitionId !== "string" && typeof partitionId !== "number") {
throw new Error("'partitionId' must be of type: 'string' | 'number'.");
}
const ehSender = new EventHubSender(context, partitionId);
if (!context.senders[ehSender.address]) {
context.senders[ehSender.address] = ehSender;
}
return context.senders[ehSender.address];
}
}
exports.EventHubSender = EventHubSender;

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

@ -0,0 +1,116 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
const Constants = require("./util/constants");
const errors_1 = require("./errors");
const _1 = require(".");
/**
* Represents options can be set during the creation of a event hub receiver.
* Defines a position of an @link~EventData in the event hub partition.
* @class EventPosition
*/
class EventPosition {
constructor(options) {
/**
* @property {boolean} isInclusive Indicates if the current event at the specified offset is
* included or not. It is only applicable if offset is set. Default value: false.
*/
this.isInclusive = false;
if (options) {
this.offset = options.offset;
this.enqueuedTime = options.enqueuedTime;
this.sequenceNumber = options.sequenceNumber;
this.isInclusive = options.isInclusive || false;
this.customFilter = options.customFilter;
}
}
/**
* Gets the expression (filter clause) that needs to be set on the source.
* @return {string} filterExpression
*/
getExpression() {
let result;
// order of preference
if (this.offset) {
result = this.isInclusive ?
`${Constants.offsetAnnotation} >= '${this.offset}'` :
`${Constants.offsetAnnotation} > '${this.offset}'`;
}
else if (this.sequenceNumber) {
result = this.isInclusive ?
`${Constants.sequenceNumberAnnotation} >= '${this.sequenceNumber}'` :
`${Constants.sequenceNumberAnnotation} > '${this.sequenceNumber}'`;
}
else if (this.enqueuedTime) {
const time = (this.enqueuedTime instanceof Date) ? this.enqueuedTime.getTime() : this.enqueuedTime;
result = `${Constants.enqueuedTimeAnnotation} > '${time}'`;
}
else if (this.customFilter) {
result = this.customFilter;
}
if (!result) {
throw errors_1.translate({ condition: _1.ErrorNameConditionMapper.ArgumentError, description: "No starting position was set in the EventPosition." });
}
return result;
}
/**
* Creates a position at the given offset.
* @param {string} offset The offset of the data relative to the Event Hub partition stream.
* The offset is a marker or identifier for an event within the Event Hubs stream.
* The identifier is unique within a partition of the Event Hubs stream.
* @param {boolean} isInclusive If true, the specified event is included;
* otherwise the next event is returned. Default: false.
* @return {EventPosition} EventPosition
*/
static fromOffset(offset, isInclusive) {
return new EventPosition({ offset: offset, isInclusive: isInclusive });
}
/**
* Creates a position at the given sequence number.
* @param {number} sequenceNumber The logical sequence number of the event within the partition stream of the Event Hub.
* @param {boolean} isInclusive If true, the specified event is included;
* otherwise the next event is returned. Default false.
* @return {EventPosition} EventPosition
*/
static fromSequenceNumber(sequenceNumber, isInclusive) {
return new EventPosition({ sequenceNumber: sequenceNumber, isInclusive: isInclusive });
}
/**
* Creates a position at the given enqueued time.
* @param {Date | number} enqueuedTime The enqueue time. This value represents the actual time of enqueuing the message.
* @param {boolean} isInclusive If true, the specified event is included; otherwise the next event is returned.
* @return {EventPosition} EventPosition
*/
static fromEnqueuedTime(enqueuedTime) {
return new EventPosition({ enqueuedTime: enqueuedTime });
}
/**
* Creates a position based on the given custom filter.
* @param {string} customFilter The cutom filter expression that needs to be applied on the receiver. This should be used
* only when one of the other methods `fromOffset()`, `fromSequenceNumber()`, `fromEnqueuedTime()` is not applicable for
* your scenario.
*/
static withCustomFilter(customFilter) {
return new EventPosition({ customFilter: customFilter });
}
/**
* Returns the position for the start of a stream. Provide this position in receiver creation to
* start receiving from the first available event in the partition.
* @return {EventPosition} EventPosition
*/
static fromStart() {
return EventPosition.fromOffset(EventPosition.startOfStream);
}
/**
* Returns the position for the end of a stream. Provide this position in receiver creation to
* start receiving from the next available event in the partition after the receiver is created.
* @return {EventPosition} EventPosition
*/
static fromEnd() {
return EventPosition.fromOffset(EventPosition.endOfStream);
}
}
EventPosition.startOfStream = "-1";
EventPosition.endOfStream = "@latest";
exports.EventPosition = EventPosition;

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

@ -0,0 +1,23 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
var eventData_1 = require("./eventData");
exports.EventData = eventData_1.EventData;
var connectionConfig_1 = require("./connectionConfig");
exports.ConnectionConfig = connectionConfig_1.ConnectionConfig;
var errors_1 = require("./errors");
exports.EventHubsError = errors_1.EventHubsError;
exports.ErrorNameConditionMapper = errors_1.ErrorNameConditionMapper;
exports.ConditionStatusMapper = errors_1.ConditionStatusMapper;
exports.ConditionErrorNameMapper = errors_1.ConditionErrorNameMapper;
var eventHubClient_1 = require("./eventHubClient");
exports.EventHubClient = eventHubClient_1.EventHubClient;
var eventPosition_1 = require("./eventPosition");
exports.EventPosition = eventPosition_1.EventPosition;
var token_1 = require("./auth/token");
exports.TokenType = token_1.TokenType;
var constants_1 = require("./util/constants");
exports.aadEventHubsAudience = constants_1.aadEventHubsAudience;
var utils_1 = require("./util/utils");
exports.delay = utils_1.delay;

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

@ -0,0 +1,163 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const uuid = require("uuid/v4");
const rheaPromise = require("./rhea-promise");
const Constants = require("./util/constants");
const debugModule = require("debug");
const rpc_1 = require("./rpc");
const utils_1 = require("./util/utils");
const Buffer = require("buffer/").Buffer;
const debug = debugModule("azure:event-hubs:management");
/**
* @class ManagementClient
* Descibes the EventHubs Management Client that talks
* to the $management endpoint over AMQP connection.
*/
class ManagementClient {
/**
* @constructor
* Instantiates the management client.
* @param entityPath - The name/path of the entity (hub name) for which the management request needs to be made.
*/
constructor(entityPath) {
this.entityPath = entityPath;
this.managementLock = `${Constants.managementRequestKey}-${uuid()}`;
this.entityPath = entityPath;
}
/**
* Provides the eventhub runtime information.
* @method getHubRuntimeInformation
* @param {Connection} connection - The established amqp connection
* @returns {Promise<EventHubRuntimeInformation>}
*/
getHubRuntimeInformation(connection) {
return __awaiter(this, void 0, void 0, function* () {
const info = yield this._makeManagementRequest(connection, Constants.eventHub);
const runtimeInfo = {
path: info.name,
createdAt: new Date(info.created_at),
partitionCount: info.partition_count,
partitionIds: info.partition_ids,
type: info.type
};
debug("[%s] The hub runtime info is: %O", connection.options.id, runtimeInfo);
return runtimeInfo;
});
}
/**
* Provides an array of partitionIds.
* @method getPartitionIds
* @param {Connection} connection - The established amqp connection
* @returns {Promise<Array<string>>}
*/
getPartitionIds(connection) {
return __awaiter(this, void 0, void 0, function* () {
const runtimeInfo = yield this.getHubRuntimeInformation(connection);
return runtimeInfo.partitionIds;
});
}
/**
* Provides information about the specified partition.
* @method getPartitionInformation
* @param {Connection} connection - The established amqp connection
* @param {(string|number)} partitionId Partition ID for which partition information is required.
*/
getPartitionInformation(connection, partitionId) {
return __awaiter(this, void 0, void 0, function* () {
if (!partitionId || (partitionId && typeof partitionId !== "string" && typeof partitionId !== "number")) {
throw new Error("'partitionId' is a required parameter and must be of type: 'string' | 'number'.");
}
const info = yield this._makeManagementRequest(connection, Constants.partition, partitionId);
const partitionInfo = {
beginningSequenceNumber: info.begin_sequence_number,
hubPath: info.name,
lastEnqueuedOffset: info.last_enqueued_offset,
lastEnqueuedTimeUtc: new Date(info.last_enqueued_time_utc),
lastSequenceNumber: info.last_enqueued_sequence_number,
partitionId: info.partition,
type: info.type
};
debug("[%s] The partition info is: %O.", connection.options.id, partitionInfo);
return partitionInfo;
});
}
/**
* Closes the AMQP management session to the Event Hub for this client,
* returning a promise that will be resolved when disconnection is completed.
* @return {Promise<void>}
*/
close() {
return __awaiter(this, void 0, void 0, function* () {
try {
if (this._mgmtReqResLink) {
yield rheaPromise.closeSession(this._mgmtReqResLink.session);
debug("Successfully closed the management session.");
this._mgmtReqResLink = undefined;
}
}
catch (err) {
const msg = `An error occurred while closing the management session: ${err}`;
debug(msg);
throw new Error(msg);
}
});
}
_init(connection, endpoint, replyTo) {
return __awaiter(this, void 0, void 0, function* () {
if (!this._mgmtReqResLink) {
const rxopt = { source: { address: endpoint }, name: replyTo, target: { address: replyTo } };
debug("Creating a session for $management endpoint");
this._mgmtReqResLink = yield rpc_1.createRequestResponseLink(connection, { target: { address: endpoint } }, rxopt);
debug("[%s] Created sender '%s' and receiver '%s' links for $management endpoint.", connection.options.id, this._mgmtReqResLink.sender.name, this._mgmtReqResLink.receiver.name);
}
});
}
/**
* @private
* Helper method to make the management request
* @param {Connection} connection - The established amqp connection
* @param {string} type - The type of entity requested for. Valid values are "eventhub", "partition"
* @param {string | number} [partitionId] - The partitionId. Required only when type is "partition".
*/
_makeManagementRequest(connection, type, partitionId) {
return __awaiter(this, void 0, void 0, function* () {
if (partitionId && typeof partitionId !== "string" && typeof partitionId !== "number") {
throw new Error("'partitionId' is a required parameter and must be of type: 'string' | 'number'.");
}
try {
const endpoint = Constants.management;
const replyTo = uuid();
const request = {
body: Buffer.from(JSON.stringify([])),
message_id: uuid(),
reply_to: replyTo,
application_properties: {
operation: Constants.readOperation,
name: this.entityPath,
type: `${Constants.vendorString}:${type}`
}
};
if (partitionId && type === Constants.partition) {
request.application_properties.partition = partitionId;
}
yield utils_1.defaultLock.acquire(this.managementLock, () => { return this._init(connection, endpoint, replyTo); });
return rpc_1.sendRequest(connection, this._mgmtReqResLink, request);
}
catch (err) {
debug("An error occurred while making the request to $management endpoint: %O", err);
throw err;
}
});
}
}
exports.ManagementClient = ManagementClient;

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

@ -0,0 +1,89 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const errors_1 = require("./errors");
const _1 = require(".");
const debugModule = require("debug");
const debug = debugModule("azure:event-hubs:retry");
function isDelivery(obj) {
let result = false;
if (obj && typeof obj.id === "number" && typeof obj.settled === "boolean" &&
typeof obj.remote_settled === "boolean" && typeof obj.format === "number") {
result = true;
}
return result;
}
/**
* It will attempt to linearly retry an operation specified number of times with a specified
* delay in between each retry. The retries will only happen if the error is retryable.
*
* @param {Promise<T>} operation The operation that needs to be retried.
* @param {number} [times] Number of times the operation needs to be retried in case of error. Default: 3.
* @param {number} [delayInSeconds] Amount of time to wait in seconds before making the next attempt. Default: 15.
*
* @return {Promise<T>} Promise<T>.
*/
function retry(operation, times, delayInSeconds) {
return __awaiter(this, void 0, void 0, function* () {
if (!operation || typeof operation !== "function") {
throw new Error("'operation' is a required parameter and must be of type 'function'.");
}
if (times && typeof times !== "number") {
throw new Error("'times' must be of type 'number'.");
}
if (delayInSeconds && typeof delayInSeconds !== "number") {
throw new Error("'delayInSeconds' must be of type 'number'.");
}
if (!times)
times = 3;
if (!delayInSeconds)
delayInSeconds = 15;
let lastError;
let result;
let success = false;
for (let i = 0; i < times; i++) {
const j = i + 1;
debug("Retry attempt number: %d", j);
try {
result = yield operation();
success = true;
debug("Success, after attempt number: %d.", j);
if (!isDelivery(result)) {
debug("Success result: %O", result);
}
break;
}
catch (err) {
if (!err.translated) {
err = errors_1.translate(err);
}
lastError = err;
debug("Error occured in attempt number %d: %O", j, err);
if (lastError.retryable) {
debug("Sleeping for %d seconds.", delayInSeconds);
yield _1.delay(delayInSeconds * 1000);
continue;
}
else {
break;
}
}
}
if (success) {
return result;
}
else {
throw lastError;
}
});
}
exports.retry = retry;

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

@ -0,0 +1,436 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
const rhea = require("rhea");
const debugModule = require("debug");
const debug = debugModule("rhea-promise");
/**
* 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.
*/
function connect(options) {
return new Promise((resolve, reject) => {
const connection = rhea.connect(options);
function removeListeners(connection) {
connection.removeListener("connection_open", onOpen);
connection.removeListener("connection_close", onClose);
connection.removeListener("disconnected", onClose);
}
function onOpen(context) {
removeListeners(connection);
process.nextTick(() => {
debug("Resolving the promise with amqp connection.");
resolve(connection);
});
}
function onClose(context) {
removeListeners(connection);
debug(`Error occurred while establishing amqp connection.`, context.connection.error);
reject(context.connection.error);
}
connection.once("connection_open", onOpen);
connection.once("connection_close", onClose);
connection.once("disconnected", onClose);
});
}
exports.connect = connect;
/**
* 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.
*/
function closeConnection(connection) {
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) => {
if (connection.is_open()) {
function onClose(context) {
connection.removeListener("connection_close", onClose);
process.nextTick(() => {
debug("Resolving the promise as the connection has been successfully closed.");
resolve();
});
}
function onError(context) {
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();
}
});
}
exports.closeConnection = closeConnection;
/**
* 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.
*/
function createSession(connection) {
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) {
session.removeListener("session_open", onOpen);
session.removeListener("session_close", onClose);
}
function onOpen(context) {
removeListeners(session);
process.nextTick(() => {
debug("Resolving the promise with amqp session.");
resolve(session);
});
}
function onClose(context) {
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();
});
}
exports.createSession = createSession;
/**
* 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.
*/
function closeSession(session) {
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) => {
if (session.is_open()) {
function onClose(context) {
session.removeListener("session_close", onClose);
process.nextTick(() => {
debug("Resolving the promise as the amqp session has been closed.");
resolve();
});
}
function onError(context) {
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();
}
});
}
exports.closeSession = closeSession;
/**
* 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.
*/
function createSender(session, options) {
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) {
sender.removeListener("sendable", onOpen);
sender.removeListener("sender_close", onClose);
}
function onOpen(context) {
removeListeners(session);
process.nextTick(() => {
debug(`Resolving the promise with amqp sender "${sender.name}".`);
resolve(sender);
});
}
function onClose(context) {
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);
});
}
exports.createSender = createSender;
/**
* 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.
*/
function createSenderWithHandlers(session, onError, options) {
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) {
sender.removeListener("sendable", onOpen);
sender.removeListener("sender_close", onClose);
}
function onOpen(context) {
removeListeners(session);
process.nextTick(() => {
debug(`Resolving the promise with amqp sender "${sender.name}".`);
resolve(sender);
});
}
function onClose(context) {
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);
});
}
exports.createSenderWithHandlers = createSenderWithHandlers;
/**
* 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.
*/
function closeSender(sender) {
if (!sender || (sender && typeof sender !== "object")) {
throw new Error("sender is a required parameter and must be of type 'object'.");
}
return new Promise((resolve, reject) => {
if (sender.is_open()) {
function onClose(context) {
sender.removeListener("sender_close", onClose);
process.nextTick(() => {
debug("Resolving the promise as the amqp sender has been closed.");
resolve();
});
}
function onError(context) {
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();
}
});
}
exports.closeSender = closeSender;
/**
* 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.
*/
function createReceiver(session, options) {
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) {
receiver.removeListener("receiver_open", onOpen);
receiver.removeListener("receiver_close", onClose);
}
function onOpen(context) {
removeListeners(receiver);
process.nextTick(() => {
debug(`Resolving the promise with amqp receiver "${receiver.name}".`);
resolve(receiver);
});
}
function onClose(context) {
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);
});
}
exports.createReceiver = createReceiver;
/**
* 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.
*/
function createReceiverWithHandlers(session, onMessage, onError, options) {
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) {
receiver.removeListener("receiver_open", onOpen);
receiver.removeListener("receiver_close", onClose);
}
function onOpen(context) {
removeListeners(receiver);
process.nextTick(() => {
debug(`Resolving the promise with amqp receiver "${receiver.name}".`);
resolve(receiver);
});
}
function onClose(context) {
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);
});
}
exports.createReceiverWithHandlers = createReceiverWithHandlers;
/**
* 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.
*/
function closeReceiver(receiver) {
if (!receiver || (receiver && typeof receiver !== "object")) {
throw new Error("receiver is a required parameter and must be of type 'object'.");
}
return new Promise((resolve, reject) => {
if (receiver.is_open()) {
function onClose(context) {
receiver.removeListener("receiver_close", onClose);
process.nextTick(() => {
debug("Resolving the promise as the amqp receiver has been closed.");
resolve();
});
}
function onError(context) {
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();
}
});
}
exports.closeReceiver = closeReceiver;
/**
* Defines a mapping for Http like response status codes for different status-code values provided by an AMQP broker.
* @enum AmqpResponseStatusCode
*/
var AmqpResponseStatusCode;
(function (AmqpResponseStatusCode) {
AmqpResponseStatusCode[AmqpResponseStatusCode["Continue"] = 100] = "Continue";
AmqpResponseStatusCode[AmqpResponseStatusCode["SwitchingProtocols"] = 101] = "SwitchingProtocols";
AmqpResponseStatusCode[AmqpResponseStatusCode["OK"] = 200] = "OK";
AmqpResponseStatusCode[AmqpResponseStatusCode["Created"] = 201] = "Created";
AmqpResponseStatusCode[AmqpResponseStatusCode["Accepted"] = 202] = "Accepted";
AmqpResponseStatusCode[AmqpResponseStatusCode["NonAuthoritativeInformation"] = 203] = "NonAuthoritativeInformation";
AmqpResponseStatusCode[AmqpResponseStatusCode["NoContent"] = 204] = "NoContent";
AmqpResponseStatusCode[AmqpResponseStatusCode["ResetContent"] = 205] = "ResetContent";
AmqpResponseStatusCode[AmqpResponseStatusCode["PartialContent"] = 206] = "PartialContent";
AmqpResponseStatusCode[AmqpResponseStatusCode["Ambiguous"] = 300] = "Ambiguous";
AmqpResponseStatusCode[AmqpResponseStatusCode["MultipleChoices"] = 300] = "MultipleChoices";
AmqpResponseStatusCode[AmqpResponseStatusCode["Moved"] = 301] = "Moved";
AmqpResponseStatusCode[AmqpResponseStatusCode["MovedPermanently"] = 301] = "MovedPermanently";
AmqpResponseStatusCode[AmqpResponseStatusCode["Found"] = 302] = "Found";
AmqpResponseStatusCode[AmqpResponseStatusCode["Redirect"] = 302] = "Redirect";
AmqpResponseStatusCode[AmqpResponseStatusCode["RedirectMethod"] = 303] = "RedirectMethod";
AmqpResponseStatusCode[AmqpResponseStatusCode["SeeOther"] = 303] = "SeeOther";
AmqpResponseStatusCode[AmqpResponseStatusCode["NotModified"] = 304] = "NotModified";
AmqpResponseStatusCode[AmqpResponseStatusCode["UseProxy"] = 305] = "UseProxy";
AmqpResponseStatusCode[AmqpResponseStatusCode["Unused"] = 306] = "Unused";
AmqpResponseStatusCode[AmqpResponseStatusCode["RedirectKeepVerb"] = 307] = "RedirectKeepVerb";
AmqpResponseStatusCode[AmqpResponseStatusCode["TemporaryRedirect"] = 307] = "TemporaryRedirect";
AmqpResponseStatusCode[AmqpResponseStatusCode["BadRequest"] = 400] = "BadRequest";
AmqpResponseStatusCode[AmqpResponseStatusCode["Unauthorized"] = 401] = "Unauthorized";
AmqpResponseStatusCode[AmqpResponseStatusCode["PaymentRequired"] = 402] = "PaymentRequired";
AmqpResponseStatusCode[AmqpResponseStatusCode["Forbidden"] = 403] = "Forbidden";
AmqpResponseStatusCode[AmqpResponseStatusCode["NotFound"] = 404] = "NotFound";
AmqpResponseStatusCode[AmqpResponseStatusCode["MethodNotAllowed"] = 405] = "MethodNotAllowed";
AmqpResponseStatusCode[AmqpResponseStatusCode["NotAcceptable"] = 406] = "NotAcceptable";
AmqpResponseStatusCode[AmqpResponseStatusCode["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired";
AmqpResponseStatusCode[AmqpResponseStatusCode["RequestTimeout"] = 408] = "RequestTimeout";
AmqpResponseStatusCode[AmqpResponseStatusCode["Conflict"] = 409] = "Conflict";
AmqpResponseStatusCode[AmqpResponseStatusCode["Gone"] = 410] = "Gone";
AmqpResponseStatusCode[AmqpResponseStatusCode["LengthRequired"] = 411] = "LengthRequired";
AmqpResponseStatusCode[AmqpResponseStatusCode["PreconditionFailed"] = 412] = "PreconditionFailed";
AmqpResponseStatusCode[AmqpResponseStatusCode["RequestEntityTooLarge"] = 413] = "RequestEntityTooLarge";
AmqpResponseStatusCode[AmqpResponseStatusCode["RequestUriTooLong"] = 414] = "RequestUriTooLong";
AmqpResponseStatusCode[AmqpResponseStatusCode["UnsupportedMediaType"] = 415] = "UnsupportedMediaType";
AmqpResponseStatusCode[AmqpResponseStatusCode["RequestedRangeNotSatisfiable"] = 416] = "RequestedRangeNotSatisfiable";
AmqpResponseStatusCode[AmqpResponseStatusCode["ExpectationFailed"] = 417] = "ExpectationFailed";
AmqpResponseStatusCode[AmqpResponseStatusCode["UpgradeRequired"] = 426] = "UpgradeRequired";
AmqpResponseStatusCode[AmqpResponseStatusCode["InternalServerError"] = 500] = "InternalServerError";
AmqpResponseStatusCode[AmqpResponseStatusCode["NotImplemented"] = 501] = "NotImplemented";
AmqpResponseStatusCode[AmqpResponseStatusCode["BadGateway"] = 502] = "BadGateway";
AmqpResponseStatusCode[AmqpResponseStatusCode["ServiceUnavailable"] = 503] = "ServiceUnavailable";
AmqpResponseStatusCode[AmqpResponseStatusCode["GatewayTimeout"] = 504] = "GatewayTimeout";
AmqpResponseStatusCode[AmqpResponseStatusCode["HttpVersionNotSupported"] = 505] = "HttpVersionNotSupported";
})(AmqpResponseStatusCode = exports.AmqpResponseStatusCode || (exports.AmqpResponseStatusCode = {}));

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

@ -0,0 +1,249 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const os = require("os");
const process = require("process");
const debugModule = require("debug");
const uuid = require("uuid/v4");
const utils_1 = require("./util/utils");
const rhea_promise_1 = require("./rhea-promise");
const Constants = require("./util/constants");
const connectionContext_1 = require("./connectionContext");
const errors_1 = require("./errors");
const retry_1 = require("./retry");
const debug = debugModule("azure:event-hubs:rpc");
function createRequestResponseLink(connection, senderOptions, receiverOptions) {
return __awaiter(this, void 0, void 0, function* () {
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 = yield rhea_promise_1.createSession(connection);
const [sender, receiver] = yield Promise.all([
rhea_promise_1.createSender(session, senderOptions),
rhea_promise_1.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
};
});
}
exports.createRequestResponseLink = createRequestResponseLink;
function createReceiverLink(connection, receiverOptions) {
return __awaiter(this, void 0, void 0, function* () {
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 = yield rhea_promise_1.createSession(connection);
const receiver = yield rhea_promise_1.createReceiver(session, receiverOptions);
debug("[%s] Successfully created the receiver link on a dedicated session for it.", connection.options.id);
return {
session: session,
receiver: receiver
};
});
}
exports.createReceiverLink = createReceiverLink;
function createReceiverLinkWithHandlers(options) {
return __awaiter(this, void 0, void 0, function* () {
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 = yield rhea_promise_1.createSession(options.connection);
const receiver = yield rhea_promise_1.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
};
});
}
exports.createReceiverLinkWithHandlers = createReceiverLinkWithHandlers;
function createSenderLink(connection, senderOptions) {
return __awaiter(this, void 0, void 0, function* () {
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 = yield rhea_promise_1.createSession(connection);
const sender = yield rhea_promise_1.createSender(session, senderOptions);
debug("[%s] Successfully created the sender link on a dedicated session for it.", connection.options.id);
return {
session: session,
sender: sender
};
});
}
exports.createSenderLink = createSenderLink;
function createSenderLinkWithHandlers(options) {
return __awaiter(this, void 0, void 0, function* () {
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 = yield rhea_promise_1.createSession(options.connection);
const sender = yield rhea_promise_1.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
};
});
}
exports.createSenderLinkWithHandlers = createSenderLinkWithHandlers;
function sendRequest(connection, link, request, timeoutInSeconds) {
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 = new Promise((resolve, reject) => {
let waitTimer;
let timeOver = false;
const messageCallback = (context) => {
// remove the event listener as this will be registered next time when someone makes a request.
link.receiver.removeListener(Constants.message, messageCallback);
const code = context.message.application_properties[Constants.statusCode];
const desc = context.message.application_properties[Constants.statusDescription];
const errorCondition = 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 || errors_1.ConditionStatusMapper[code] || "amqp:internal-error";
const e = {
condition: condition,
description: desc
};
return reject(errors_1.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 = `The request with message_id "${request.message_id}" to "${address}" ` +
`endpoint timed out. Please try again later.`;
const e = {
condition: errors_1.ConditionStatusMapper[408],
description: desc
};
return reject(errors_1.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_1.retry(() => sendRequestPromise);
}
exports.sendRequest = sendRequest;
/**
* 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<void>}
*/
function open(context, useSaslPlain) {
return __awaiter(this, void 0, void 0, function* () {
try {
yield utils_1.defaultLock.acquire("connect", () => { return _open(context, useSaslPlain); });
}
catch (err) {
debug(err);
throw err;
}
});
}
exports.open = open;
function _open(context, useSaslPlain) {
return __awaiter(this, void 0, void 0, function* () {
if (useSaslPlain && typeof useSaslPlain !== "boolean") {
throw new Error("'useSaslPlain' must be of type 'boolean'.");
}
if (!context.connection) {
const connectOptions = {
transport: Constants.TLS,
host: context.config.host,
hostname: context.config.host,
username: context.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": connectionContext_1.ConnectionContext.userAgent
}
};
if (useSaslPlain) {
connectOptions.password = context.config.sharedAccessKey;
}
debug("Dialing the amqp connection with options.", connectOptions);
context.connection = yield rhea_promise_1.connect(connectOptions);
context.connectionId = context.connection.options.id;
debug("Successfully established the amqp connection '%s'.", context.connectionId);
}
});
}

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

@ -0,0 +1,126 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const debugModule = require("debug");
const eventHubReceiver_1 = require("./eventHubReceiver");
const Constants = require("./util/constants");
const debug = debugModule("azure:event-hubs:receiverstreaming");
class ReceiveHandler {
/**
* Creates an instance of the ReceiveHandler.
* @constructor
* @param {EventHubReceiver} receiver The underlying EventHubReceiver.
*/
constructor(receiver) {
this._receiver = receiver;
this.name = receiver ? receiver.name : "ReceiveHandler";
}
/**
* @property {ReceiverRuntimeInfo} runtimeInfo The receiver runtime info. This property will only
* be enabled when `enableReceiverRuntimeMetric` option is set to true in the
* `client.receiveOnMessage()` method.
* @readonly
*/
get runtimeInfo() {
return this._receiver ? this._receiver.runtimeInfo : undefined;
}
/**
* Stops the underlying EventHubReceiver from receiving more messages.
* @return {Promise<void>} Promise<void>
*/
stop() {
return __awaiter(this, void 0, void 0, function* () {
if (this._receiver) {
yield this._receiver.close();
}
});
}
}
exports.ReceiveHandler = ReceiveHandler;
/**
* Describes the streaming receiver where the user can receive the message
* by providing handler functions.
* @class StreamingReceiver
* @extends EventHubReceiver
*/
class StreamingReceiver extends eventHubReceiver_1.EventHubReceiver {
/**
* Instantiate a new receiver from the AMQP `Receiver`. Used by `EventHubClient`.
*
* @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.
*/
constructor(context, partitionId, options) {
super(context, partitionId, options);
}
/**
* Starts the receiver by establishing an AMQP session and an AMQP receiver link on the session.
*
* @param {OnMessage} onMessage The message handler to receive event data objects.
* @param {OnError} onError The error handler to receive an error that occurs while receivin messages.
*/
receiveOnMessage(onMessage, onError) {
if (!onMessage || typeof onMessage !== "function") {
throw new Error("'onMessage' is a required parameter and must be of type 'function'.");
}
if (!onError || typeof onError !== "function") {
throw new Error("'onError' is a required parameter and must be of type 'function'.");
}
this._onMessage = onMessage;
this._onError = onError;
if (!this._isOpen()) {
this._init().catch((err) => {
this._onError(err);
});
}
else {
// It is possible that the receiver link has been established due to a previous receive() call. If that
// is the case then add message and error event handlers to the receiver. When the receiver will be closed
// 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);
debug("[%s] Receiver '%s', set the prefetch count to 1000 and " +
"providing a credit of the same amount.", this._context.connectionId, this.name);
}
}
/**
* Creates a streaming receiver.
* @static
*
* @param {ConnectionContext} context The connection context.
* @param {string | number} partitionId The partitionId to receive events from.
* @param {ReceiveOptions} [options] Receive options.
*/
static create(context, partitionId, options) {
const sReceiver = new StreamingReceiver(context, partitionId, options);
context.receivers[sReceiver.name] = sReceiver;
return sReceiver;
}
}
exports.StreamingReceiver = StreamingReceiver;

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

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

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

@ -0,0 +1,34 @@
"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
const AsyncLock = require("async-lock");
function parseConnectionString(connectionString) {
return connectionString.split(';').reduce((acc, part) => {
const splitIndex = part.indexOf('=');
return Object.assign({}, acc, { [part.substring(0, splitIndex)]: part.substring(splitIndex + 1) });
}, {});
}
exports.parseConnectionString = parseConnectionString;
/**
* Gets a new instance of the async lock with desired settings.
* @param {AsyncLockOptions} [options] The async lock options.
*/
function getNewAsyncLock(options) {
return new AsyncLock(options);
}
exports.getNewAsyncLock = getNewAsyncLock;
/**
* @constant {AsyncLock} defaultLock The async lock instance with default settings.
*/
exports.defaultLock = new AsyncLock();
/**
* A wrapper for setTimeout that resolves a promise after t milliseconds.
* @param {number} t - The number of milliseconds to be delayed.
* @param {T} value - The value to be resolved with after a timeout of t milliseconds.
* @returns {Promise<T>} - Resolved promise
*/
function delay(t, value) {
return new Promise((resolve) => setTimeout(() => resolve(value), t));
}
exports.delay = delay;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -7,14 +7,11 @@ export {
export { Delivery } 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 } from "./eventHubClient";
export {
EventProcessorHost, OnEphClose, OnEphMessage, OnEphOpen, PartitionFilter,
ConnectionStringBasedOptions, EventProcessorOptions
} from "./eph";
export { EventHubClient, ReceiveOptions, ClientOptionsBase, ClientOptions } from "./eventHubClient";
export { EventPosition } from "./eventPosition";
export { DataTransformer, DefaultDataTransformer } from "./dataTransformer";
export { EventHubPartitionRuntimeInformation, EventHubRuntimeInformation } from "./managementClient";

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

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

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

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

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

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

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

@ -43,5 +43,5 @@ export const defaultPrefetchCount = 1000;
export const reconnectLimit = 100;
export const packageJsonInfo = {
name: "azure-event-hubs-js",
version: "0.1.1"
version: "0.2.0"
};

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

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

@ -1,6 +1,6 @@
{
"name": "azure-event-hubs",
"version": "0.1.2",
"version": "0.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -186,31 +186,6 @@
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
"integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4="
},
"azure-storage": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/azure-storage/-/azure-storage-2.8.2.tgz",
"integrity": "sha1-GctjKx5WMK6uVEUaZNrBYLFlyEg=",
"requires": {
"browserify-mime": "~1.2.9",
"extend": "~1.2.1",
"json-edm-parser": "0.1.2",
"md5.js": "1.3.4",
"readable-stream": "~2.0.0",
"request": "~2.83.0",
"underscore": "~1.8.3",
"uuid": "^3.0.0",
"validator": "~9.4.1",
"xml2js": "0.2.8",
"xmlbuilder": "0.4.3"
},
"dependencies": {
"extend": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz",
"integrity": "sha1-oPX9bPyDpf5J72mNYOyKYk3UV2w="
}
}
},
"babel-code-frame": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
@ -281,11 +256,6 @@
"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
"dev": true
},
"browserify-mime": {
"version": "1.2.9",
"resolved": "https://registry.npmjs.org/browserify-mime/-/browserify-mime-1.2.9.tgz",
"integrity": "sha1-rrGvKN5sDXpqLOQK22j/GEIq8x8="
},
"buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
@ -617,15 +587,6 @@
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
"dev": true
},
"hash-base": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
"integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
"requires": {
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"hawk": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz",
@ -671,7 +632,8 @@
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
},
"is-buffer": {
"version": "2.0.2",
@ -688,11 +650,6 @@
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@ -720,14 +677,6 @@
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
"optional": true
},
"json-edm-parser": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/json-edm-parser/-/json-edm-parser-0.1.2.tgz",
"integrity": "sha1-HmCw/vG8CvZ7wNFG393lSGzWFbQ=",
"requires": {
"jsonparse": "~1.2.0"
}
},
"json-schema": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
@ -743,11 +692,6 @@
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
},
"jsonparse": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.2.0.tgz",
"integrity": "sha1-XAxWhRBxYOcv50ib3eoLRMK8Z70="
},
"jsprim": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
@ -791,15 +735,6 @@
"integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==",
"dev": true
},
"md5.js": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz",
"integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=",
"requires": {
"hash-base": "^3.0.0",
"inherits": "^2.0.1"
}
},
"mime-db": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
@ -977,11 +912,6 @@
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"process-nextick-args": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
},
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
@ -992,19 +922,6 @@
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
"integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A=="
},
"readable-stream": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
"integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "~1.0.0",
"process-nextick-args": "~1.0.6",
"string_decoder": "~0.10.x",
"util-deprecate": "~1.0.1"
}
},
"request": {
"version": "2.83.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz",
@ -1056,11 +973,6 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
},
"sax": {
"version": "0.5.8",
"resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz",
"integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE="
},
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
@ -1111,11 +1023,6 @@
"tweetnacl": "~0.14.0"
}
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
},
"stringstream": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
@ -1244,21 +1151,11 @@
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI="
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"uuid": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz",
"integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA=="
},
"validator": {
"version": "9.4.1",
"resolved": "https://registry.npmjs.org/validator/-/validator-9.4.1.tgz",
"integrity": "sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA=="
},
"verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
@ -1275,19 +1172,6 @@
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
},
"xml2js": {
"version": "0.2.8",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.2.8.tgz",
"integrity": "sha1-m4FpCTFjH/CdGVdUn69U9PmAs8I=",
"requires": {
"sax": "0.5.x"
}
},
"xmlbuilder": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-0.4.3.tgz",
"integrity": "sha1-xGFLp04K0ZbmCcknLNnh3bKKilg="
},
"xmldom": {
"version": "0.1.27",
"resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz",

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

@ -1,6 +1,6 @@
{
"name": "azure-event-hubs",
"version": "0.1.2",
"version": "0.2.0",
"description": "Azure Event Hubs SDK for Node.js",
"author": "Microsoft Corporation",
"license": "MIT",
@ -13,7 +13,6 @@
"ms-rest": "^2.3.3",
"ms-rest-azure": "^2.5.5",
"rhea": "^0.2.12",
"azure-storage": "^2.8.2",
"uuid": "^3.2.1"
},
"devDependencies": {
@ -49,4 +48,4 @@
"bugs": {
"url": "http://github.com/Azure/azure-event-hubs-node/issues"
}
}
}

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

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

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

@ -5,8 +5,6 @@ import * as chai from "chai";
const should = chai.should();
import * as assert from "assert";
const isBuffer = require("is-buffer");
// import * as debugModule from "debug";
// const debug = debugModule("azure:event-hubs:datatransformer-spec");
import { DefaultDataTransformer } from "../lib";
describe("DataTransformer", function () {

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

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

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

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

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

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

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

40
client/tsconfig.json Normal file
Просмотреть файл

@ -0,0 +1,40 @@
{
"compilerOptions": {
"module": "commonjs",
"preserveConstEnums": true,
"sourceMap": true,
"newLine": "LF",
"target": "es6",
"moduleResolution": "node",
"noImplicitReturns": true,
"outDir": "dist/lib",
"allowJs": false,
"noUnusedLocals":true,
"strict": true,
"strictPropertyInitialization": true,
"declaration": true,
"declarationDir": "./typings/lib",
"lib": [
"es2015",
"dom",
"dom.iterable",
"es5",
"es6",
"es7",
"esnext",
"esnext.asynciterable",
"es2015.iterable"
]
},
"compileOnSave": true,
"exclude": [
"node_modules",
"testhub",
"typings/**",
"examples/**",
"processor"
],
"include": [
"./lib/**/*.ts"
]
}

114
client/tslint.json Normal file
Просмотреть файл

@ -0,0 +1,114 @@
{
"rules": {
"member-ordering": [
true,
{
"order": "instance-sandwich"
}
],
"typedef": [
true,
"call-signature",
"parameter",
"property-declaration",
"member-variable-declaration"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "onespace",
"index-signature": "onespace",
"parameter": "onespace",
"property-declaration": "onespace",
"variable-declaration": "onespace"
}
],
"prefer-const": true,
"no-construct": true,
"no-duplicate-variable": true,
"no-empty": true,
"no-invalid-this": true,
"no-null-keyword": true,
"no-string-literal": true,
"no-switch-case-fall-through": true,
"no-unsafe-finally": true,
"no-unused-expression": true,
"no-unused-variable": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"no-floating-promises": true,
"triple-equals": [
true,
"allow-null-check",
"allow-undefined-check"
],
"use-isnan": true,
"eofline": true,
"indent": [
true,
"spaces"
],
"no-trailing-whitespace": true,
"arrow-parens": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"interface-name": [
true,
"never-prefix"
],
"jsdoc-format": true,
"new-parens": true,
"one-line": [
true,
"check-catch",
"check-finally",
"check-else",
"check-open-brace"
],
"one-variable-per-declaration": [
true,
"ignore-for-loop"
],
"semicolon": [
true,
"always"
],
"curly": [
true,
"ignore-same-line"
],
"variable-name": [
true,
"check-format",
"allow-leading-underscore",
"ban-keywords"
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-module",
"check-seperator",
"check-type"
]
},
"cliOptions": {
"exclude": [
"examples",
"tests",
"testhub/*.ts",
"processor"
]
}
}

43
processor/.npmignore Normal file
Просмотреть файл

@ -0,0 +1,43 @@
# git #
.git*
# Node #
node_modules/
# Test folder #
tests/
testhub/
# Tools and dev scripts #
tools/
tasks/
scripts/
gruntfile.js
gulpfile.js
# Nuget packages #
.nuget/
packages/
packages.config
# VS #
.ntvs_analysis.*
*.sln
*.obj
# Examples #
examples/
# vscode #
.vscode/
.env
sample.env
# misc #
contribute.md
lib/
tsconfig.json
tslint.json
webpack.config.ts
.idea/
changelog.md
samples/

68
processor/README.md Normal file
Просмотреть файл

@ -0,0 +1,68 @@
azure-event-processor-host
================
_This SDK is currently in preview._
Azure Event Processor Host helps you efficiently receive events from an EventHub. It will create EventHub Receivers
across all the partitions in the consumer group of an EventHub and provide you messages received across
all the partitions. It will checkpoint metadata about the received messages at regular interval in an
Azure Storage Blob. This makes it easy to continue receiving messages from where you left at a later time.
- **Node.js version: 6.x or higher.** We would encourage you to install the latest available LTS version from https://nodejs.org.
## Installation ##
```bash
npm install azure-event-processor-host
```
## Examples
- Examples can be found over [here](./examples).
## Usage
```js
const { EventProcessorHost, delay } = require("azure-event-processor-host");
const storageConnectionString = "STORAGE_CONNECTION_STRING";
const ehconnectionString = "EVENTHUB_CONNECTION_STRING";
const entityPath = "EVENTHUB_NAME";
const path = process.env[entityPath] || "";
const storageCS = process.env[storageConnectionString];
const ehCS = process.env[ehconnectionString];
async function main() {
// Create the Event Processo Host
const eph = EventProcessorHost.createFromConnectionString(
EventProcessorHost.createHostName("my-host"),
storageCS!,
ehCS!,
{
eventHubPath: path
}
);
// Message event handler
const onMessage = (context/*PartitionContext*/, data /*EventData*/) => {
console.log(">>>>> Rx message from '%s': '%s'", context.partitionId, data.body);
return context.checkpoint();
};
// Error event handler
const onError = (error) => {
console.log(">>>>> Received Error: %O", error);
};
// Register the event handlers
eph.on(EventProcessorHost.message, onMessage);
eph.on(EventProcessorHost.error, onError);
// start the EPH
await eph.start();
// After some time let' say 2 minutes
await delay(120000);
// This will stop the EPH.
await eph.stop();
}
main().catch((err) => {
console.log(err);
});
```

6
processor/changelog.md Normal file
Просмотреть файл

@ -0,0 +1,6 @@
## 2018-05-02 0.1.0
- First version of `azure-event-processor-host` based on the new `azure-event-hubs` sdk.
- This client library makes it easier to manage receivers for an EventHub.
- You can checkpoint the received data to an Azure Storage Blob. The processor does checkpointing
on your behalf at regular intervals. This makes it easy to start receiving events from the point you
left at a later time.

42
processor/examples/eph.ts Normal file
Просмотреть файл

@ -0,0 +1,42 @@
import { EventProcessorHost, OnEphMessage, EventData, PartitionContext, OnEphError, delay } from "../lib";
const storageConnectionString = "STORAGE_CONNECTION_STRING";
const ehconnectionString = "EVENTHUB_CONNECTION_STRING";
const entityPath = "EVENTHUB_NAME";
const path = process.env[entityPath] || "";
const storageCS = process.env[storageConnectionString];
const ehCS = process.env[ehconnectionString];
async function main() {
// Create the Event Processo Host
const eph = EventProcessorHost.createFromConnectionString(
EventProcessorHost.createHostName("my-host"),
storageCS!,
ehCS!,
{
eventHubPath: path
}
);
// Message event handler
const onMessage: OnEphMessage = (context: PartitionContext, data: EventData) => {
console.log(">>>>> Rx message from '%s': '%s'", context.partitionId, data.body);
return context.checkpoint();
};
// Error event handler
const onError: OnEphError = (error) => {
console.log(">>>>> Received Error: %O", error);
};
// Register the event handlers
eph.on(EventProcessorHost.message, onMessage);
eph.on(EventProcessorHost.error, onError);
// start the EPH
await eph.start();
// After some time let' say 2 minutes
await delay(120000);
// This will stop the EPH.
await eph.stop();
}
main().catch((err) => {
console.log(err);
});

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

@ -0,0 +1,11 @@
{
"name": "example-eh",
"private": true,
"version": "0.0.1",
"dependencies": {
"azure-event-processor-host": "*"
},
"scripts": {
"build": "tsc -p ./tsconfig.json --baseUrl ../"
}
}

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

@ -0,0 +1,39 @@
{
"compilerOptions": {
"module": "commonjs",
"preserveConstEnums": true,
"sourceMap": false,
"newLine": "LF",
"target": "es6",
"moduleResolution": "node",
"noImplicitReturns": true,
"outDir": "js",
"allowJs": false,
"noUnusedLocals": true,
"strict": true,
"strictPropertyInitialization": true,
"declaration": false,
"baseUrl": "../",
"paths": {
"azure-event-processor-host": ["./lib/"]
},
"lib": [
"es2015",
"dom",
"dom.iterable",
"es5",
"es6",
"es7",
"esnext",
"esnext.asynciterable",
"es2015.iterable"
]
},
"compileOnSave": true,
"exclude": [
"node_modules",
],
"include": [
"./*.ts"
]
}

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

@ -3,8 +3,8 @@
import * as debugModule from "debug";
import { createBlobService, BlobService, ServiceResponse } from "azure-storage";
import * as Constants from "../util/constants";
import { defaultLock } from "../util/utils";
import * as Constants from "azure-event-hubs/lib/util/constants";
import { defaultLock } from "azure-event-hubs/lib/util/utils";
const debug = debugModule("azure:event-hubs:eph:lease");

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

@ -4,7 +4,7 @@
import { EventEmitter } from "events";
import * as debugModule from "debug";
import { BlobLease, Lease } from "./blobLease";
import { Dictionary } from "../eventData";
import { Dictionary } from "azure-event-hubs";
const debug = debugModule("azure:event-hubs:eph:lease-manager");
/**

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

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

@ -6,18 +6,17 @@ import * as debugModule from "debug";
import { BlobLeaseManager, LeaseManager } from "./blobLeaseManager";
import { BlobLease, Lease } from "./blobLease";
import { PartitionContext } from "./partitionContext";
import { EventHubClient, ClientOptions } from "../eventHubClient";
import { EventEmitter } from "events";
import {
TokenProvider, EventHubRuntimeInformation, EventHubPartitionRuntimeInformation,
ReceiveOptions, EventPosition, OnMessage, OnError, EventHubsError
} from "..";
import { Dictionary, EventData } from "../eventData";
ReceiveOptions, EventPosition, OnMessage, OnError, EventHubsError, EventHubClient, ClientOptions,
Dictionary, EventData, ReceiveHandler, ClientOptionsBase
} from "azure-event-hubs";
import {
ApplicationTokenCredentials, UserTokenCredentials,
DeviceTokenCredentials, MSITokenCredentials
} from "ms-rest-azure";
import { ReceiveHandler } from "../streamingReceiver";
const debug = debugModule("azure:event-hubs:eph:host");
/**
@ -54,7 +53,7 @@ export interface StartEPHOptions {
* Describes the optional parameters that can be provided for creating an EventProcessorHost.
* @interface EventProcessorOptions
*/
export interface EventProcessorOptions {
export interface EventProcessorOptions extends ClientOptionsBase {
/**
* @property {EventPosition} initialOffset This is only used when then receiver is being created
* for the very first time and there is no checkpoint data in the blob. For this option to be
@ -71,7 +70,8 @@ export interface EventProcessorOptions {
*/
leaseManager?: LeaseManager;
/**
* @property {string} [leasecontainerName] Azure Storage container name for use by built-in lease and checkpoint manager.
* @property {string} [leasecontainerName] Azure Storage container name for use by built-in
* lease and checkpoint manager.
*/
leasecontainerName?: string;
/**
@ -415,7 +415,8 @@ export class EventProcessorHost extends EventEmitter {
options?: ConnectionStringBasedOptions): EventProcessorHost {
if (!options) options = {};
const ehCOptions: ClientOptions = {
tokenProvider: options.tokenProvider
tokenProvider: options.tokenProvider,
dataTransformer: options.dataTransformer
};
return new EventProcessorHost(hostName, storageConnectionString,
EventHubClient.createFromConnectionString(eventHubConnectionString, options.eventHubPath,
@ -448,7 +449,11 @@ export class EventProcessorHost extends EventEmitter {
eventHubPath: string,
credentials: ApplicationTokenCredentials | UserTokenCredentials | DeviceTokenCredentials | MSITokenCredentials,
options?: EventProcessorOptions): EventProcessorHost {
if (!options) options = {};
const ehcOptions: ClientOptionsBase = {
dataTransformer: options.dataTransformer
};
return new EventProcessorHost(hostName, storageConnectionString,
EventHubClient.createFromAadTokenCredentials(namespace, eventHubPath, credentials), options);
EventHubClient.createFromAadTokenCredentials(namespace, eventHubPath, credentials, ehcOptions), options);
}
}

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

@ -9,3 +9,4 @@ export { PartitionContext } from "./partitionContext";
export { CheckpointInfo } from "./checkpointInfo";
export { Lease } from "./blobLease";
export { LeaseManager, LeaseWithDuration } from "./blobLeaseManager";
export { delay, EventData, OnError as OnEphError } from "azure-event-hubs";

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

@ -4,8 +4,8 @@
import * as debugModule from "debug";
const debug = debugModule("azure:event-hubs:eph:partition");
import * as uuid from "uuid/v4";
import { EventData } from "../";
import * as Constants from "../util/constants";
import { EventData } from "azure-event-hubs";
import * as Constants from "azure-event-hubs/lib/util/constants";
import { BlobLease } from "./blobLease";
import { CheckpointInfo } from "./checkpointInfo";

2072
processor/package-lock.json сгенерированный Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

46
processor/package.json Normal file
Просмотреть файл

@ -0,0 +1,46 @@
{
"name": "azure-event-processor-host",
"version": "0.1.0",
"description": "Azure Event Processor Host (Event Hubs) SDK for Node.js",
"author": "Microsoft Corporation",
"license": "MIT",
"main": "./dist/lib/index.js",
"types": "./typings/lib/index.d.ts",
"dependencies": {
"azure-event-hubs": "../client",
"azure-storage": "^2.8.2",
"uuid": "^3.2.1"
},
"devDependencies": {
"@types/uuid": "^3.4.3",
"@types/debug": "^0.0.30",
"@types/node": "^8.0.37",
"@types/chai": "^4.1.2",
"@types/chai-as-promised": "^7.1.0",
"@types/mocha": "^5.0.0",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"mocha": "^5.0.5",
"ts-node": "^5.0.1",
"tslint": "^5.9.1",
"typescript": "^2.8.3",
"dotenv": "^5.0.1",
"@types/dotenv": "^4.0.3"
},
"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",
"prepare": "npm run build"
},
"homepage": "http://github.com/azure/azure-event-hubs-node",
"repository": {
"type": "git",
"url": "https://github.com/azure/azure-event-hubs-node.git"
},
"bugs": {
"url": "http://github.com/Azure/azure-event-hubs-node/issues"
}
}

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

@ -8,11 +8,11 @@ chai.use(chaiAsPromised);
import * as debugModule from "debug";
const should = chai.should();
const debug = debugModule("azure:event-hubs:eph-spec");
import { EventHubClient, EventProcessorHost, OnEphOpen, EventData, EventPosition, delay } from "../lib";
import { EventHubClient, EventData, EventPosition, delay } from "../../client/lib";
import * as dotenv from "dotenv";
import { BlobLeaseManager } from "../lib/eph/blobLeaseManager";
import { PartitionContext, OnEphMessage } from "../lib/eph";
import { executePromisesSequentially } from "../lib/util/utils";
import { BlobLeaseManager } from "../lib/blobLeaseManager";
import { PartitionContext, OnEphMessage, EventProcessorHost, OnEphOpen } from "../lib";
import { executePromisesSequentially } from "../../client/lib/util/utils";
dotenv.config();
describe("EPH", function () {

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

@ -8,8 +8,8 @@ chai.use(chaiAsPromised);
import * as debugModule from "debug";
import * as uuid from "uuid/v4";
const debug = debugModule("azure:event-hubs:lease-spec");
import { BlobLease } from "../lib/eph/blobLease";
import { parseConnectionString, StorageConnectionStringModel } from "../lib/util/utils";
import { BlobLease } from "../lib/blobLease";
import { parseConnectionString, StorageConnectionStringModel } from "azure-event-hubs/lib/util/utils";
import * as dotenv from "dotenv";
dotenv.config();

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

@ -8,9 +8,9 @@ chai.use(chaiAsPromised);
import * as debugModule from "debug";
import * as uuid from "uuid/v4";
const debug = debugModule("azure:event-hubs:lease-spec");
import { BlobLease } from "../lib/eph/blobLease";
import { BlobLeaseManager } from "../lib/eph/blobLeaseManager";
import { parseConnectionString, StorageConnectionStringModel } from "../lib/util/utils";
import { BlobLease } from "../lib/blobLease";
import { BlobLeaseManager } from "../lib/blobLeaseManager";
import { parseConnectionString, StorageConnectionStringModel } from "azure-event-hubs/lib/util/utils";
import * as dotenv from "dotenv";
dotenv.config();

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше