Reorg for client and EPH
This commit is contained in:
Родитель
6b4b66192a
Коммит
0251e9326d
14
.travis.yml
14
.travis.yml
|
@ -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
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).
|
|
@ -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"
|
||||
};
|
|
@ -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 () {
|
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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/
|
|
@ -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);
|
||||
});
|
||||
```
|
||||
|
||||
|
|
@ -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.
|
|
@ -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";
|
||||
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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();
|
||||
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче