Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DNASNS-2660] NodeJS stream client maintenance #44

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": ["airbnb/base"],
"extends": ["eslint:recommended", "prettier"],
"rules": {
"no-console": "off",
"max-len": [2, {"code": 205, "tabWidth": 4, "ignoreUrls": true}],
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ node_modules
/DowJonesDNA.json
/r-d.sh
/fix_git.sh

.envrc
.vscode/**
.DS_Store
47 changes: 29 additions & 18 deletions Listener.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ class Listener {
* Leave as null or undefined if you
* want to use the default.
*/
listen(onMessageCallback, subscription, userErrorHandling = false) {
listen(onMessageCallback, subscription, usingAsyncFunction = true) {
return this.apiService.getStreamingCredentials().then((credentials) => {
this.initialize(credentials);
this.readyListener(onMessageCallback, subscription, userErrorHandling);
this.readyListener(onMessageCallback, subscription, usingAsyncFunction);
return true;
}).catch((err) => {
if (err.message) {
Expand All @@ -55,7 +55,7 @@ class Listener {
});
}

readyListener(onMessageCallback, subscriptionId, userErrorHandling) {
readyListener(onMessageCallback, subscriptionId, usingAsyncFunction) {
const sub = subscriptionId || this.defaultSubscriptionId;

if (!sub || sub.length <= 0) {
Expand All @@ -66,37 +66,36 @@ class Listener {

console.log(`Listening to subscription: ${subscriptionFullName}`);

const onMessageTryCatch = (msg) => {
try {
onMessageCallback(msg);
msg.ack();
} catch (err) {
console.error(`Error from callback: ${err}\n`);
msg.nack();
throw err;
}
};
const onMessagePromise = (msg) => {
onMessageCallback(msg)
.then(() => {
msg.ack();
})
.catch(err => {
console.error(`On callback ${err.message}`);
msg.nack();
});
}

const onMessageUserHandling = (msg) => {
onMessageCallback(msg, err => {
if (err) {
console.error(`Error from callback: ${err}`);
msg.nack();
throw err;
} else {
msg.ack();
}
});
}

const onMessage = (userErrorHandling) ? onMessageUserHandling : onMessageTryCatch;
const onMessage = (usingAsyncFunction) ? onMessagePromise : onMessageUserHandling;

const pubsubSubscription = this.pubsubClient.subscription(subscriptionFullName);
this.pubsubSubscription = this.pubsubClient.subscription(subscriptionFullName);

this.apiService.getAccountInfo().then(accountInfo =>
this.checkDocCountExceeded(sub, accountInfo.max_allowed_document_extracts));

pubsubSubscription.get().then((data) => {
this.pubsubSubscription.get().then((data) => {
const pubsubSub = data[0];
pubsubSub.on('message', onMessage);
pubsubSub.on('error', (subErr) => {
Expand All @@ -118,7 +117,6 @@ class Listener {
'However, you won\'t lose access to any documents that have already been added to the queue.\n' +
'These will continue to be streamed to you.\n' +
'Contact your account administrator with any questions or to upgrade your account limits.';
const interval = 300000;
this.apiService.isStreamDisabled(subscriptionId).then((isDisabled) => {
if (isDisabled) {
console.error(streamDisabledMsg);
Expand All @@ -127,6 +125,19 @@ class Listener {
console.error(err);
});
}

closeListener() {
if (this.pubsubSubscription) {
if (this.pubsubSubscription.isOpen) {
this.pubsubSubscription.close();
console.log('Closing the listener.')
} else {
console.warn("The listener is already closed.");
}
} else {
console.warn("There's no opened subscription to close. Call listen method first.");
}
}
}

module.exports = Listener;
179 changes: 135 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# dj-dna-streaming-javascript

DNA Streaming Client - written in Javascript.

## How To Use

#### Installing
### Installing

This project is an NPM module. That means it can be installed as a kind of library for your main project. To do this go to your main project's root. At the command line execute the following:

Expand All @@ -13,28 +14,28 @@ npm install git+https://git@github.com/dowjones/dj-dna-streams-javascript.git --

Alternatively you can simply check out this project from Git.

#### Authentication
### Authentication

User Key

#### Configuring The App
### Configuring The App

There are three ways to pass configuration variables to the app. Please note that environment variables (Option 1) will override values provided in the `customerConfig.json` file (Option 2).
They will not override values passed directly to the `Listener` constructor (Option 3).

Option 1. Set environment variables.
#### Option 1. Set environment variables.

###### User Key
Export the following envirnment variables:

**USER_KEY**
Dow Jones provided user key.

**SUBSCRIPTION_ID**
This environment variable holds the subscription ID.

Option 2. Modify the 'customerConfig.json' file. In this project's root you will find the 'customerConfig.json' file. Add your credentials and subscription ID. Ensure your additions follow the JSON data format conventions.
This environment variable holds the subscription ID.

###### User Key
#### Option 2. Modifying `customerConfig.json`

In this project's root you will find the `customerConfig.json` file. Add your credentials and subscription ID. Ensure your additions follow the JSON data format conventions.

```
{
Expand All @@ -43,14 +44,14 @@ Option 2. Modify the 'customerConfig.json' file. In this project's root you will
}
```

or
#### Option 3. Passing values as function arguments.

Option 3: Passing values as function arguments. Specifically you can pass either the service account credentials and/or subscription ID. When you start a listener you can pass the service account crendentials to the Listener constructor as an object with the field "user_key", like so:
Specifically you can pass either the service account credentials and/or subscription ID. When you start a listener you can pass the service account crendentials to the Listener constructor as an object with the field "user_key", like so:

~~~~
var Listener = require('dj-dna-streaming-javascript').Listener;
const Listener = require('dj-dna-streaming-javascript').Listener;

var onMessageCallback = function(msg) {
const onMessageCallback = async function(msg) {
console.log('One incoming message:' + JSON.stringify(msg.data));
};

Expand All @@ -68,28 +69,28 @@ This will override both the environment variable and the configuration file serv
If you want to pass the subscription ID via function arguments, take a look at the following code:

~~~~
var Listener = require('dj-dna-streaming-javascript').Listener;
const Listener = require('dj-dna-streaming-javascript').Listener;

var onMessageCallback = function(msg) {
const onMessageCallback = async function(msg) {
console.log('One incoming message:' + JSON.stringify(msg.data));
};

var subscriptionId = 'abcdefghi123';
const subscriptionId = 'abcdefghi123';

const listener = new Listener();
listener.listen(onMessageCallback, subscriptionId);
~~~~


#### Running the Demo Code
### Running the Demo Code

This modules comes with demonstration code. To execute the demo code, configure your app (See _Configuring the App_ section above). Then execute the following:

~~~
npm run demo
~~~

##### Docker Demo
### Docker Demo

To execute the demo code in a Docker container, perform the following steps.

Expand All @@ -101,58 +102,148 @@ Step 1: Build the docker image. Execute the following command line:

Step 2: Run the docker image

###### User Key

~~~
docker run -it \
-e USER_KEY="<your user key>" \
-e SUBSCRIPTION_ID="<your subscription ID>" \
dj-dna-streaming-javascript
~~~

###### Client Credentials
~~~
docker run -it \
-e USER_ID="<your user ID>" \
-e CLIENT_ID="<your client ID>" \
-e PASSWORD="<your password>" \
-e SUBSCRIPTION_ID="<your subscription ID>" \
dj-dna-streaming-javascript
~~~
## Writing Your Own Code

The following is some very basic code. Use it to listen to a DNA subscription. It assumes you have correctly configured the app. (See the *Configuring The App* section above).

You can use two patterns to consume the messages from the subscription. The first option is using an async function or function returning a Promise or theanable:

#### Writing Your Own Code
### Async Function, Promise or Theanable

The following is some very basic code. Use it to listen to a DNA subscription. It assumes you have configured the app correct. (See the *Configuring The App* section above).
Write an async function or function returning a Promise/Theanable processing the messages. When the
promise is resolved the message is acknowledged, in case the promise is rejected the message will be
not acknowledged, so it can be processed again.

~~~~
var Listener = require('dj-dna-streaming-javascript').Listener;
const Listener = require('dj-dna-streaming-javascript').Listener;

var onMessageCallback = function(msg) {
const onMessageCallback = async function(msg) {
console.log('One incoming message:' + JSON.stringify(msg.data));
};

const listener = new Listener();
listener.listen(onMessageCallback);
~~~~

###### Error Handling
### Callback pattern

If your callback fails, the message will be nack'd and the listener will rethrow the error. If you wish to write your own error handling for callbacks then set the `userErrorHandling` parameter to true. This allows you to use an error handler callback to force the callback handler to nack messages. The following is a very basic example illustrating how this may work.
Write a callback function with two parameters, the first one being the message. The second one is a function which must be called with null as a parameter if the message is correctly processed or an error. If the parameter is null when calling handleErr, the message will be acknowledged, if not, the message will be not acknowledged.

~~~~
var Listener = require('dj-dna-streaming-javascript').Listener;
var onMessageCallback = function((msg, handleErr) {
let err = null;
try {
const Listener = require('dj-dna-streaming-javascript').Listener;

const onMessageCallback = (msg, handleErr) => {
let err = null;
try {
console.log('One incoming message:' + JSON.stringify(msg.data));
} catch (e) {
err = e
};
} catch (e) {
err = e
};

handleErr(err)
};

const listener = new Listener();
listener.listen(onMessageCallback, null, false);
~~~~

### Shutting down the listener

The listener have a method to stop receiving new messages. After a set time you can call this method or you can add a termination signal listener to call it and close cleanly the listener.

#### Waiting a set time

~~~~
const Listener = require('dj-dna-streaming-javascript').Listener;

const onMessageCallback = async function(msg) {
console.log('One incoming message:' + JSON.stringify(msg.data));
};

const listener = new Listener();
listener.listen(onMessageCallback, my_subscription_id, true);
listener.listen(onMessageCallback);

// Listen to messages for 10 seconds

setTimeout(() => {
listener.closeListener();
}, 10000);
~~~~

#### Ading a listener to the SIGTERM or SIGINT signals

~~~~
const Listener = require('dj-dna-streaming-javascript').Listener;

const onMessageCallback = async function(msg) {
console.log('One incoming message:' + JSON.stringify(msg.data));
};

const listener = new Listener();
listener.listen(onMessageCallback);

// This method calls the listener's closeListener method.
const terminationHandler = () => {
listener.closeListener();
};

// Adding this method as a handler of SIGTERM or SIGINT
process.on('SIGINT', terminationHandler);
process.on('SIGTERM', terminationHandler);
~~~~

### Migrating from a synchronous callback function

The latest version of the client require the callback function to conform to the callback pattern or using a function returning a Promise or Theanable.

When having a synchronous callback function as the following:
~~~~~
const oldSynchronousCallback = (msg) => {
console.log('One incoming message:' + JSON.stringify(msg.data));
}
~~~~~

#### Create Promise from the old callback function:

~~~~
const Listener = require('dj-dna-streaming-javascript').Listener;

const newAsyncCallback = (msg) => {
return new Promise((resolve, reject) => {
try {
resolve(oldSynchronousCallback(msg));
} catch (e) {
reject(e);
}
});
};

const listener = new Listener();
listener.listen(newAsyncCallback);
~~~~

#### Use the callback pattern:
~~~~
const Listener = require('dj-dna-streaming-javascript').Listener;

const onMessageCallback = (msg, handleErr) => {
let err = null;
try {
oldSynchronousCallback(msg);
} catch (e) {
err = e
};

handleErr(err)
};

const listener = new Listener();
listener.listen(onMessageCallback, null, false);
~~~~
Loading