# Messaging Platform SDK

An SDK to facilitate agent and consumer interactions on LivePerson's Messaging Platform from Node.js and web applications. Can be used to create bots, system tests, or custom UIs.

For more information about specific classes, functions, or enums see the [API Reference](https://l1905.gitlab.io/conversational-cloud-engineering/messaging-assist/lp-messaging-sdk/).

[View the changelog here.](https://developers.liveperson.com/messaging-platform-sdk-changelog.html)

Messaging Platform SDK has replaced [Messaging Agent SDK](https://www.npmjs.com/package/node-agent-sdk) (aka Node Agent SDK) as the recommended method for interacting with Messaging Platform from a node.js application.
See the [Feature Comparison](https://developers.liveperson.com/messaging-platform-sdk-sdk-feature-comparison.html) for more information.
For information about converting a Node Agent SDK based project to use Messaging Platform SDK, see our [Conversion Guide](https://developers.liveperson.com/messaging-platform-sdk-node-agent-sdk-conversion-guide.html).

## Table of Contents

* [How To Install](#how-to-install)
* [Quick Start](#quick-start)
  * [A simple consumer conversation](#a-simple-consumer-conversation)
  * [A simple agent conversation listener bot](#a-simple-agent-conversation-listener-bot)
* [Commonly Used Features](#commonly-used-features)
  * [Application Tracking](#application-tracking)
  * [Conversations](#conversations)
    * [Create Conversation](#create-conversation)
    * [Conversation Functions](#conversation-functions)
      * [Join and Leave](#join-and-leave)
      * [Transfer](#transfer)
      * [Close](#close)
      * [Resume conversation](#resume-conversation)
      * [Get Latest Consumer Message](#get-latest-consumer-message)
  * [Rings](#rings)
    * [Agent Routing Tasks aka "Rings"](#agent-routing-tasks-aka-rings)
    * [Accepting conversation on the ring](#accepting-conversation-on-the-ring)
    * [FAQ](#faq)
  * [Conversation Events](#conversation-events)
    * [Conversation Event: close](#conversation-event-close)
    * [Conversation Event: transfer-skill](#conversation-event-transfer-skill)
    * [Conversation Event: transfer-agent](#conversation-event-transfer-agent)
    * [Conversation Event: back-to-queue](#conversation-event-back-to-queue)
    * [Conversation Event: consumer-step-up](#conversation-event-consumer-step-up)
    * [Conversation Event: participant-added](#conversation-event-participant-added)
    * [Conversation Event: participant-removed](#conversation-event-participant-removed)
  * [Messages](#messages)
    * [Markdown in Messages](#markdown-in-messages)
    * [Message Events](#message-events)
    * [Previous messages](#previous-messages)
    * [Message is sent by current user](#message-is-sent-by-current-user)
    * [Loading all previous messages](#loading-all-previous-messages)
    * [Message Metadata](#message-metadata)
    * [Message quick replies](#message-quick-replies)
    * [Error Handling](#error-handling)
  * [File Sharing](#file-sharing)
    * [Upload a file from Node.js](#upload-a-file-from-nodejs)
    * [Download a file from Node.js](#download-a-file-from-nodejs)
    * [Upload a file from browser](#upload-a-file-from-browser)
    * [Download a file in browser](#download-a-file-in-browser)
* [Advanced Topics](#advanced-topics)
  * [Background Process Errors](#background-process-errors)
  * [Arbitrary websocket requests](#arbitrary-websocket-requests)
  * [Notification Events](#notification-events)
  * [Notification Types](#notification-types)
    * [Message Event Notification](#message-event-notification)
    * [Conversation State Notification](#conversation-state-notification)
    * [Routing Notification](#routing-notification)
  * [Notification order](#notification-order)
  * [Subscriptions](#subscriptions)
    * [The default subscription](#the-default-subscription)
    * [Creating manual subscriptions](#creating-manual-subscriptions)
    * [Query properties](#query-properties)
  * [Connection Maintenance](#connection-maintenance)
    * [Automatically mark bot as offline upon disconnect](#automatically-mark-bot-as-offline-upon-disconnect)
  * [Brand Authentication](#brand-authentication)
    * [Different authentication mechanisms](#different-authentication-mechanisms)
    * [Brand Authentication Token Process](#brand-authentication-token-process)
    * [Brand Authentication with existing bearer token](#brand-authentication-with-existing-bearer-token)
    * [Brand Authentication using TokenMaintainer](#brand-authentication-using-tokenmaintainer)
  * [Continuing an anonymous user session between two connections](#continuing-an-anonymous-user-session-between-two-connections)
  * [Client Properties](#client-properties)
  * [Conversation Context and Campaign Info](#conversation-context-and-campaign-info)
  * [Consumer Auth Flow](#consumer-auth-flow)
  * [Getting a User Profile](#getting-a-user-profile)
  * [Set User Profile](#set-user-profile)
  * [Consumer Step Up](#consumer-step-up)
  * [Message statistics](#message-statistics)
  * [Using proxies](#using-proxies)
  * [Response Timeout](#response-timeout)
  * [Combined conversation handling](#combined-conversation-handling)
* [Features Not yet supported](#features-not-yet-supported)

## How To Install

To install as a dependency, run the following from a terminal window:

`npm install lp-messaging-sdk`

## Quick Start

### A simple consumer conversation

```javascript
const lpm = require("lp-messaging-sdk");

const connection = lpm.createConnection({
    appId: 'quick_start', // TODO: please change to something unique to your application
    accountId: '12345678',
    userType: lpm.UserType.CONSUMER
});

// log any internal errors (auth errors, etc)
connection.on('error', err => {
    console.error(err);
});

// connect & open conversation
await connection.open();

// optionally set the consumer's name information
await connection.setUserProfile({firstName: 'firstName', lastName: 'lastName', nickName: 'nickName'});

// create conversation
const conversation = await connection.createConversation();

// setup a message notification listener
conversation.on('message', message => {
    console.log(JSON.stringify(message.body));
});

// send a message
await conversation.sendMessage('test');

// close the main dialog
await conversation.close();

// close the connection
await connection.close();
```

### A simple agent conversation listener bot
```javascript
const lpm = require("lp-messaging-sdk");

// define the auth data
const accountId = '12345678';
const authData = {
    username: 'bot1',
    appKey: '1a804df636f347bgcb4974a1ea3e2a91',
    secret: 'e15d540710838b40',
    accessToken: 'ccf8gf5bb346f3a95245e9b4798695f2',
    accessTokenSecret: '876c7425f81c5160'
};

// create the connection object
const connection = lpm.createConnection({
    appId: 'quick_start', // TODO: please change to something unique to your application
    accountId,
    userType: lpm.UserType.BRAND,
    authData
});

// log any internal errors (auth errors, etc)
connection.on('error', err => {
    console.error(err);
});

// setup the conversation event
// this event will fire whenever the bot is informed of a new conversation
connection.on('conversation', async conversation => {

    // join the conversation as "AGENT" role
    await conversation.join(lpm.ParticipantRole.AGENT);
    
    // listen for messages from the consumer
    conversation.on('message', message => {
        
        // ignore all messages not from the consumer
        if (message.participant.role !== lpm.ParticipantRole.CONSUMER) return;
        
        console.log(message.body);
        
        // send a simple response
        conversation.sendMessage('hello');
    });
    
    // listen for the close event
    conversation.on('close', () => {
        console.log('conversation closed');
    });
    
});

// make the connection
await connection.open();
```

## Commonly Used Features

### Application Tracking

In order to help us provide the best support, we require that you choose an `appId` for your application.
`appId` should only contain characters `a-z` and/or the special characters: `-_.`.
This should be passed in the createConnection function along with the version of your application if available.


```javascript
const consumerConnection = lpm.createConnection({
    appId: 'quick_start',
    appVersion: '1.6.2',
    accountId: '123456',
    userType: UserType.CONSUMER
});
```

---

### Conversations

#### Create Conversation

When a consumer connection calls `createConversation` with no arguments, the conversation will be created in the default skill: "-1".
```javascript
const conversation = await connection.createConversation();
expect(conversation.skill.skillId).toEqual('-1');
```

To create a conversation in a specific skill, simply pass the skillId as an argument when calling createConversation.

```javascript
const conversation = await connection.createConversation({skillId: '12345678'});
```

See [Conversation Context and Campaign Info](#conversation-context-and-campaign-info) for two other arguments that can be passed to createConversation.

---

#### Conversation Functions

##### Join and Leave

One way to join a conversation is to invoke the join function on that conversation, passing the role that the current user should join the conversation as. Commonly used roles are `ASSIGNED_AGENT`, `AGENT`, `MANAGER`, and `CONTROLLER`.
The other main way to join a conversation as the assigned agent is to [accept a ring](#agent-routing-tasks-aka-rings).
```javascript
await conversation.join(lpm.ParticipantRole.ASSIGNED_AGENT);
```

To leave a conversation, use the leave function.
```javascript
await conversation.leave();
```

##### Transfer

To transfer a conversation, use the transfer function. The transfer function will remove the assigned agent from the conversation (if present) and then transfer the conversation to the specified skill or agent. Keep in mind that if you try to transfer without having an open conversation you will get an error.

NOTE: when transferring to a specific agent, specify the whole agent id, i.e. `accountId.agentId`, instead of just `agentId`.

```javascript
// transfer to a specific skill
await conversation.transfer({skillId: '1111111'});

// transfer to a specific agent 
await conversation.transfer({agentId: '2222222.5555555'});
```

##### Close

You have the option to close either the main dialog or the entire conversation. Here's how each works:

- Closing the Main Dialog:
When you close the main dialog, the conversation remains open as long as there are other dialogs configured.
For instance, post-conversation surveys might run in a separate dialog after the main dialog ends. If no additional
dialog is configured, the conversation will automatically close.


- Closing the Conversation:
Closing the entire conversation prevents any further dialogs from appearing, including post-conversation surveys. This
is important to consider if you plan to configure post-conversation surveys in the future.

To close only the main dialog while leaving the conversation open, use the following method:

```javascript
await conversation.getDialog(DialogType.MAIN).close()
```

To close the entire conversation, use the close function on the `conversation` object. Be aware that doing so will disable post-conversation surveys:

```javascript
await conversation.close();
```

Alternatively, you can close a conversation by passing its conversationId to the closeConversation method of the `connection`
object. This does not require a conversation object, but keep in mind that post-conversation surveys will not run:

```javascript
const conversationId = '123456789';
await connection.closeConversation(conversationId);
```

##### Resume conversation

To resume previously closed conversation you can use the resumeConversation function

```javascript
const resumedConversation = await conversation.resumeConversation();
```

##### Get Latest Consumer Message

To get latest consumer message you can use getLatestConsumerMessage function. Note that, the function does not attempt
to retrieve messages over the network. This means the latest consumer message will only be available if was received
before.

```javascript
const latestConsumerMessage = conversation.getLatestConsumerMessage();
```

---
### Rings

#### Agent Routing Tasks aka "Rings"

Most bots are configured for the role of `MANAGER` and receive conversations by virtue of being subscribed to
all conversations, and thus they will get their conversations through the connection.on('conversation') event.

Agents and agent-type bots, on the other hand, get notified that they should handle a certain conversation through a
process called "agent routing". In this process, the bot must indicate it is open to accepting routing tasks
otherwise known as "rings" by setting their agent state to "online" and creating a routingTaskSubscription, which
will then emit `ring` events when an agent has been assigned to handle a conversation.

Note: If an agent (bot or human) accepts a ring, the agent is assigned to the corresponding conversation.
When the agent rejects the ring or lets it expire the corresponding conversation is moved back to the queue.
This means, that when multiple agents with the same skill are online, rings might not arrive immediately - because other
agents are ringed first.

When a ring is received, the agent has the option to either accept or reject the ring. Accepting the ring will add
the agent as a participant on the conversation with the role of `ASSIGNED_AGENT`.

```javascript
// connect as admin for brand
const connection = lpm.createConnection({appId: 'quick_start', userType: lpm.UserType.BRAND, accountId, authData});
await connection.open();

// agent state must be "ONLINE" in order to receive rings
await connection.setAgentState({agentState: lpm.AgentState.ONLINE});

// subscribe to routing tasks (rings)
const taskRoutingSubscription = await connection.createRoutingTaskSubscription();

// process the rings as they arrive
connection.on('ring', async ring => {
    
    // ignore old rings
    if (ring.ringState !== lpm.RingState.WAITING) return;
    
    // accept the ring
    // full conversation will appear from the connection.on('conversation') event (for now)
    await ring.accept();
    
    // or reject the ring
    // await ring.reject();
});

connection.on('conversation', conversation => {
    console.log('agent has been added to the conversation');
});
```

#### Accepting conversation on the ring

As convenience, you can fetch the conversation, which is assigned to the ring.
The default timeout is two seconds with 100 ms polling intervals.
You can set a different timeout in milliseconds when establishing the brand connection.

```javascript
// connect as admin for brand
const connection = lpm.createConnection({appId: 'quick_start', userType: lpm.UserType.BRAND, accountId, authData, ringConversationTimeout: 2000});
await connection.open();

// agent state must be "ONLINE" in order to receive rings
await connection.setAgentState({agentState: lpm.AgentState.ONLINE});

// subscribe to routing tasks (rings)
const taskRoutingSubscription = await connection.createRoutingTaskSubscription();

// process the rings as they arrive
connection.on('ring', async ring => {
    
    // ignore old rings
    if (ring.ringState !== lpm.RingState.WAITING) return;
    
    // accept the ring
    await ring.accept();

    // await fetching conversation
    const conversation = await ring.conversation();
    
    // or define callback to avoid pausing code execution
    ring.conversation().then((conversation) => {
       // process conversation and/or execute code that depends on the conversation to have already been fetched
    });

    // or reject the ring
    // await ring.reject();
});

```
---

#### FAQ

<details>
  <summary>Q: When a ring is accepted, can it be accepted by another agent? </summary>

A: A ring is always specific for one agent. It cannot be accepted twice. When a ring is accepted, the conversation is assigned to this bot or agent.
</details>

<details>
  <summary>Q: Can two different rings share the same id?</summary>

A: Two different rings always have a different id. When a conversation is sent back to the queue, a new ring for a different agent with a different id
will be created. The ring id has this pattern: dialogId_brandId_timeStampInMilliSeconds.
</details>



<details>
  <summary>Q: How is the skillId of the ring determined?</summary>

A: A skill id is related to a conversation. It is selected by a rule engine or by the engagement when the conversation is opened by a consumer.
The skill id of a ring can change when the conversation is transferred to a different skill.
</details>

<details>
  <summary>Q: What happens when an agent rejects a ring?</summary>

A: Agents assigned to the skill of the conversation are ringed one after another. If all reject the ring, the first agent will be ringed again.
For example, given three agents with skill A. When there is a new conversation for skill A and the first agent rejects the ring, then the conversation
is put back in the queue. A new ring is created for the second agent. If all three agents reject the ring, the first agent will be ringed again.
</details>




### Conversation Events

With the Messaging Platform SDK, it is possible to set up hooks for events in a conversation.

#### Conversation Event: close

To know when a conversation closes, watch the `close` event.
Please note that most conversation actions are not available for closed conversations, such as sending a message or transfer.

```javascript
conversation.on('close', async () => {
    // TODO: close action
});
```

#### Conversation Event: transfer-skill

The `transfer-skill` event will fire when the conversation is transferred from one skill to another. Usually this also involves the assigned agent being removed.

One potential use case for conversation hooks is to implement holder bots that handles conversations when the
contact center is in off hours and there are no available agents:
```javascript
conversation.on('transfer-skill', async () => {
    
    // if the new skill is in off hours
    if (conversation.skill.isInOffHours()) {
        
        // join the conversation and send a message to inform the consumer
        await conversation.join(lpm.ParticipantRole.AGENT);
        await conversation.sendMessage('we are in off hours, would you like to wait?');
    }
});
```

#### Conversation Event: transfer-agent

The `transfer-agent` event will fire when the conversation is transferred directly from one assigned agent to another.

```javascript
conversation.on('transfer-agent', async () => {
    
});
```

#### Conversation Event: back-to-queue

The `back-to-queue` event will fire when the assigned agent is removed from the conversation (without adding another) and the skill does not change.

```javascript
conversation.on('back-to-queue', async () => {
    
});
```

#### Conversation Event: consumer-step-up

The `consumer-step-up` event will fire when the consumer becomes authenticated during a conversation.

```javascript
conversation.on('consumer-step-up', async () => {
    
});
```

#### Conversation Event: participant-added

The `participant-added` event will fire whenever any participant is added to the conversation, this includes bots and managers.

It is also possible to extend the [agent greeter bot](#a-simple-agent-conversation-listener-bot) to react to new participants
on the conversation, a hook for `participant-added` event can be added:
```javascript
// listen for new participants on the conversation
conversation.on('participant-added', async addedParticipant => {

    // if a manager joins, send a private message that is hidden from the consumer
    if (addedParticipant.participant.role === lpm.ParticipantRole.MANAGER) {
        await conversation.sendPrivateMessage('a manager has joined the conversation');
    }
});
```

#### Conversation Event: participant-removed

The `participant-removed` event will fire whenever any participant is removed from the conversation.

```javascript
conversation.on('participant-removed', async (removedParticipant) => {
    
});
```

For a complete list of events emitted by the conversation object, see **Events** section of [Conversation class](https://l1905.gitlab.io/conversational-cloud-engineering/conversation-exchange-services/lp-messaging-sdk/Conversation.html)

---

### Messages

To send a message to a conversation there are several methods you can use:

```javascript
// plain text
await conversation.sendMessage('hello');

// rich text
await conversation.sendRichText(richTextObject);

// brand connections can send private messages to a conversation that consumers will not see
await conversation.sendPrivateMessage('this is private');
```

#### Markdown in Messages

Messages can include markdown encoding by wrapping the mardown in the `#md#` tag.
One common use is to use this to create a hyperlink via the markdown syntax of `[label](url)`.

```javascript
await conversation.sendMessage('view our docs #md#[here](https://developers.liveperson.com)#/md#');
```

#### Message Events

To receive messages you can watch the conversation's `message` event.
If the user is an assigned agent or a consumer, your application should call the `accept` and `read` functions in order to tell the system that the message has been accepted and read (where appropriate).
```javascript
// listen for messages from the consumer
conversation.on('message', async message => {
    
    // ignore all messages not from the consumer
    if (message.participant.role !== lpm.ParticipantRole.CONSUMER) return;
    
    // indicate that our application has received the message
    await message.accept();
    
    // write the message out to log
    console.log(message.body);
    
    // indicate that the user had read the message
    await message.read();
});
```

#### Previous messages

All received messages for a dialog will be stored in the `messages` array of that dialog.
You can iterate through that array to write these messages to the screen or a log.
```javascript
for (const message of conversation.openDialog.messages) {
    console.log(message.body);
}
```

#### Message is sent by current user

To check if a message is self sent (sent from the same user) you can use the sentByCurrentUser() method on every message.
```javascript
message.sentByCurrentUser();
```

#### Loading all previous messages

By default, `messages` will only have the messages that have been received since the conversation was joined.
For consumer connections, all messages are automatically retrieved for open conversations. For brand connections, messages
are not automatically retrieved, because this could result in excessive memory usage for accounts with a large number of
open conversations. Instead, you can call `getAllPublishEvents` on a conversation's dialog to ensure that all of its
messages have been loaded into the `messages` array.

```javascript
connection.on('conversation', async conversation => {
  // Load events for the open or main dialog
  const dialog = openDialogOrMainDialogIfAvailable(conversation)
  if (dialog) {
    await dialog.getAllPublishEvents();
  }
  // [execute conversation code]
});

/**
 * Returns the open dialog or the main dialog if the conversation is closed.
 * 
 * @param conversation of the dialog
 * @returns {(null|dialog)}
 */
function openDialogOrMainDialogIfAvailable(conversation) {
  return conversation.openDialog ?? conversation.getDialog(DialogType.MAIN);
}

```

However, you can force the SDK to automatically call `getAllPublishEvents` on all conversations as they are received, before they are emitted via the conversation event,
by passing the argument `getAllMessages: true` when calling `createConnection`
```javascript
const connection = lpm.createConnection({
    appId: 'quick_start', // TODO: please change to something unique to your application
    accountId,
    userType: lpm.UserType.BRAND,
    authData,
    getAllMessages: true
});
```

---

#### Message Metadata

To attach metadata to a message, pass it in as the second parameter to `sendMessage`, `sendRichText`, or `sendPrivateMessage`.
The structure of metadata objects is strictly validated, please contact the Messaging Platform team for more information.

```javascript
// TODO: ensure the metadata structure conforms to a valid metadata definition based on type
const metadata = {
    type: 'ExternalId',
    id: '123'
};
await conversation.sendMessage('external action 123 was taken', metadata);
```

Metadata can also be an array of metadata objects, if you need to attach more than one:
```javascript
const metadata1 = {type: 'ExternalId',id: '1'};
const metadata2 = {type: 'ExternalId',id: '2'};
await conversation.sendMessage('external action 123 was taken', [metadata1, metadata2]);
```
---

#### Message quick replies

To attach quick replies to a message, pass the object as the third parameter to `sendMessage` or `sendRichText`.
The structure of quick reply objects is strictly validated, please contact the Messaging Platform team for more information.

```javascript
// TODO: ensure the quickreplies structure conforms to a valid definition
const quickReplies = {
    "type": "quickReplies",
    "itemsPerRow": 3,
    "replies": [
        // TODO: insert quickreplies objects here
    ]
};
await conversation.sendMessage('here are some quick replies', null, quickReplies);
```

---

### Error Handling

To help you identify missed events when an error of type `conversation not in cache` occurs, the SDK provides the event itself. 
This situation can arise, for example, when a message notification is received, but the event for the conversation's creation has not yet been processed. 
You can access the event associated with the error using `error.event`.

```javascript
connection.on(`error`, err => {
    if (err.event) {
        handleMessageForUnknownConversation(err.event);
    }
})

function handleMessageForUnknownConversation(event){
  console.log(`Event with error: ${event}`)
}

```

---

### File Sharing

Accounts that have [filesharing enabled](https://community.liveperson.com/kb/articles/1130-agent-file-sharing-overview)
can upload and download files using the SDK. The article also describes the limits such as file size, formats, supported channels

To upload files, pass an [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)
to the `Conversation.uploadFile` method. An optional caption can also be passed in as the second argument.

If the upload succeeds, the SDK will then publish a message on the conversation with the uploaded file and caption (if
any), which makes it viewable by the conversation participants. For image files, the SDK will generate a preview thumbnail
that gets displayed in [supported channels](https://community.liveperson.com/kb/articles/1378-photo-sharing-user-guide).

#### Upload a file from Node.js

To upload a file In Node.js environments, access the file with the File System module to generate a buffer.

```javascript
const fs = require('fs');
const path = require('path');

// read the file into a buffer
const file = fs.readFileSync(path.resolve(__dirname, 'asset/logo.png'));

// upload the buffer
await conversation.uploadFile(file.buffer);
```

#### Download a file from Node.js

Whenever the conversation has a downloadable message with a file, the file can be downloaded.

```javascript
// set up a hook to download files in the conversation
conversation.on('message', async (msg) => {
    
    // ignore regular messages without files
    if (!msg.isDownloadable) {
        return;
    }
    
    // download the file
    const downloadedFile = await msg.downloadFile();
    
    // write the file out to disk
    const buffer = downloadedFile.data;
    const filename = downloadedFile.filename;
    fs.writeFileSync(path.resolve(__dirname, `asset/${filename}`), data);
});
```

#### Upload a file from browser

For browser implementations, the SDK supports client-side scripting with JavaScript.
To allow a user to upload a file to a conversation, first create an input tag of type `file`, an event that pulls the data from that file, and uploads it to the conversation.

```html
<div id="file-upload" style="padding: 4px">
    <input type="file" id="selected-file">
    <button id="upload" value="upload">Upload</button>
</div>

<script type="text/javascript">
    const selectedFile = document.querySelector('#selected-file');

    async function upload(buffer) {
        await conversation.uploadFile(buffer);
    }

    document.querySelector('#upload').addEventListener('click', () => {
        console.log('uploading file');

        const file = selectedFile.files[0];
        
        // create a FileReader instance and read the selected file as arrayBuffer
        let fr = new FileReader();
        fr.onload = function () {
            let data = fr.result;
            upload(data).then(() => {
                writeLine('file uploaded');
            });
        };
        fr.readAsArrayBuffer(file);
    });
</script>
```

#### Download a file in browser

To download a file, set up a message event as before, but use the `download` function below in order to cause the browser to prompt the user to save the file to disk.

```javascript

    conversation.on('message', async (msg) => {
        // ignore regular messages without files
        if (!msg.isDownloadable) {
            return;
        }
        await download(msg);
    });

    async function download(message) {
        // execute the real download
        const {data} = await message.downloadFile();
        
        // convert arrayBuffer to Blob
        const blob = new Blob([data]);
        
        // obtain the filename 
        const path = message.relativePath.split('/');
        const filename = path[path.length - 1];
        
        // create a temporary element that references the downloaded data
        let link = document.createElement('hiddenDownloadElement');
        link.href = window.URL.createObjectURL(blob);
        link.download = filename;
        
        // downloads the data as a file
        link.click();
        
        // remove the temporary element
        window.URL.revokeObjectURL(blob);
        link.remove();
    }
```

---

## Advanced Topics

### Background Process Errors

There are many active processes running under the hood of the SDK, sometimes these will encounter an error scenario.
They will do their best to recover, but it is advised that applications watch and log the error event. To do this,
simply add an event watcher to the connection and log the error as you would any other error in the application.
If you get any unexpected errors that you can't resolve on your own, please reach out to a LivePerson team member
for assistance.

```javascript
connection.on('error', err => {
    console.error(err);
});
```

---

### Arbitrary websocket requests

Our goal in making this SDK is to support all Message Platform requests through nicely designed interfaces.
However, there may be some requests or edge cases for existing requests that the SDK does not yet support.
If there is a type of request that you want to execute for which the SDK does not yet officially support, you can send the request directly using the `send` function.

The `send` function is async and will wait for the response back before completing. For example, to send a plain text message, instead of using `sendMessage` you can use the following code:
```javascript
const request = {
    type: '.ams.ms.PublishEvent',
    body: {
        dialogId: 'MY_DIALOG_ID',
        event: {
            type: 'ContentEvent',
            contentType: 'text/plain',
            message: 'hello world!'
        }
    }
};

const response = await connection.send(request);
```

---

### Notification Events

There are three different kinds of messages used in communicating with Messaging Platform: Request, Response, and Notification. For the most part, when using the SDK your application does not need to consider these, but we want to provide the information just in case it is useful.

In general, when you use the SDK to issue a command, it sends a Request to the server, the request is processed and
the server returns a Response which then triggers the awaited promise to resolve. So, Response messages are only ever
received in response to a Request.

The third type of websocket message is a notification, these are the server's method of communicating with
clients asynchronously, without a request originating from the client. They are the main reason that websockets are required, otherwise we could do all communications simply using http.

Notifications are automatically processed by the SDK and may result in one of the events mentioned above.
However, some applications might wish to examine notifications directly.
In order to do this, watch the `notification` event on a connection.

```javascript
connection.on('notification', notification => {
    console.log(`notification received of type ${notification.type}\n${JSON.stringify(notification.body, null, 2)}`);
})
```

---

### Notification Types

There are three types of Notifications that Messaging Platform can send, each of which has different conditions that must 
be met in order to receive them. Some of them involve the creation of a subscription telling Messaging Platform which 
types of notifications the application wants to receive.

#### Message Event Notification

A message event notification is sent whenever a publishEvent request is successfully processed by the Messaging Platform.
There are various types of message events: plain or rich message text from a participant, the status of a participant 
(typing, away, etc.), a file sharing event, etc. To receive messages events for a conversation, an agent simply needs to 
be a participant in the conversation. This is accomplished by joining a conversation or accepting a ring.

Consumer connections, on the other hand, must create a subscription for the conversation before they will receive these
notifications. **The SDK creates and maintains these subscriptions automatically**, there is no need to create them
manually.

#### Conversation State Notification

Conversation state notifications contain information about a conversation's state (whether it is open or closed, or which 
users are participants, etc.)

To receive conversation state notifications, a conversation subscription is required whose query encompasses that
conversation. **The SDK creates and maintains these subscriptions automatically**, there is usually no need to
[create them manually](#creating-manual-subscriptions), unless you want to see closed conversations on a brand connection, which does not apply to
most users.

#### Routing Notification

A routing task event, aka a Ring, indicating that routing has chosen the current user to handle a conversation.
To receive routing task events, a routing task subscription is required. These must be manually created, for more 
information see [Agent Routing Tasks](#agent-routing-tasks-aka-rings).

### Notification order

The SDK processes notifications in order on a best-effort basis. However, different types of notifications may sometimes 
arrive out of order. For example, a `ContentEvent` may be received before the corresponding conversation state 
notification that announces a new conversation. While such sequence errors are rare, they can occur. To maintain proper 
order, the SDK implements the following mechanisms:

- **Delayed Processing:** If a content event arrives before its corresponding conversation state update, the SDK waits 
  for one second to allow the conversation to arrive. If it doesn’t, the SDK attempts to fetch the conversation via 
  an API call. An error is emitted if both strategies fail.
- **Handling Missing Conversations:** If a message event is received before the conversation state update and the 
  conversation cannot be fetched from the API, an error (`conversation not found in cache`) is emitted. This error 
  includes the raw `event` data, which can still be processed. See [Error Handling](#error-handling) for details.
- **Unknown Participant Handling:** If a chat state event references a participant unknown to the SDK, the SDK emits a 
  warning if the participant was previously removed and does not process the event. Otherwise, it waits one second for a 
  possible conversation update. If the participant remains unknown after this, an error is emitted.
- **Coordinated Event Processing:** The SDK prevents duplicate conversation processing by coordinating events triggered 
  either by a `conversation` event or when a `ring` is accepted. This ensures each conversation is processed only once. 
  For details, see [Combined Conversation Handling](#combined-conversation-handling).

### Subscriptions

#### The default subscription

By default, after connecting the SDK will automatically create a single subscription per connection.
This subscription is available on the connection object, if you want to log all notifications that subscription
receives you can do so with this code:

```javascript
connection.defaultSubscription.on('notification', notification => {
    console.log(JSON.stringify(notification));
});
```

Each type of connection has a different default query that is used to create its default subscription:

* Brand connections use this query: ```{stage:["OPEN"]}```
* Consumer connections use this query: ```{stage:["OPEN", "CLOSE"]}```

You can also provide a different query that will be used to create the default subscription, for example if you want
your bot to only monitor conversations with an open main dialog, you would create your connection like this:
```javascript
const connection = lpm.createConnection({
    appId: 'quick_start',
    accountId,
    userType: lpm.UserType.BRAND,
    authData,
    defaultSubscriptionQuery: {state:["OPEN"], dialogTypes:["MAIN"]}
});
```

[Other possible query properties.](#query-properties)

If you don't want the SDK to create the default subscription, you can disable it by passing
createDefaultSubscription as false when creating the connection:

```javascript
const connection = lpm.createConnection({
    appId: 'quick_start',
    accountId,
    userType: lpm.UserType.BRAND,
    authData,
    createDefaultSubscription: false
});
```
Note that `skillId` is not supported in the subscription query.

#### Creating manual subscriptions

In the event that you want to create a conversation subscription manually, use the "createConversationSubscription"
function. Please note that conversation objects are shared between subscriptions, in the sense that the SDK will use
any notifications from all active subscriptions to update conversation objects.

```javascript
const query = {stage:["OPEN"], agentId:['12345.123456']};
const waitForReady = false;
const sub = await connection.createConversationSubscription({query, waitForReady});
```

`waitForReady` can be used to wait for conversations to be loaded once the connection is opened. False by default.

<div style="background-color: #f8d7da; color: #721c24; padding: 10px; border-radius: 5px; border: 1px solid #f5c6cb;">
  <strong>Notice:</strong> If <strong>agentId</strong> is not provided - the bots will try to subscribe to all open conversation for the given account.
</div>
<br>

#### Query properties

Following properties can be used to filter for conversations in your query.

| Property               | Type and example                                            | Description                                                                                                                                    |
|------------------------|-------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| 
| query.accountId        | `string`: `"3949245"`                                       | The account id.                                                                                                                                |
| query.agentId          | `Array<string>`: `["3949245.agentId1", "3949245.agentId2"]` | Filters for events of conversations for these agent ids. The id is preceded by the account id.                                                 |
| query.consumerId       | `string`: `consumerId123`                                   | Filters for events of conversations for this consumer.                                                                                         |
| query.conversationId   | `string`: `59999610-d812-4j76-907f-9905as579e5a`            | Filters for events for a specific conversation.                                                                                                |
| query.state            | `Array<string>`: `["OPEN", "CLOSE"]`                        | Filters for conversations with a MAIN dialog in given state. Possible values: `OPEN`, `CLOSE`, `LOCKED`, `CLOSING`                             |
| query.stage            | `Array<string>`: `["OPEN", "CLOSE"]`                        | Filters for conversations being in given stage. Possible values: `OPEN`, `CLOSE`, `LOCKED`, `CLOSING`                                          |
| query.dialogType       | `Array<string>`: `["MAIN"]`                                 | Filters conversation events related to given dialog type. Possible values: `MAIN`, `AGENTS`, `OTHER`, `POST_SURVEY`, `PRE_CONVERSATION_OPT_IN` |
| query.lastUpdateBefore | `number`: `1715613775114`                                   | Filters for conversations which were updated before given timestamp.                                                                           |
| query.lastUpdateAfter  | `number`: `1715613775114`                                   | Filters for conversations which were updated after given timestamp.                                                                            |

`query` is a nested object within the connection object. For example:

```json
{
    "waitForReady": true,
    "query": {
        "dialogType": ["MAIN"], 
        "agentId": ["1245.1122"]
    }
}
```

---

### Connection Maintenance

Communication with Messaging Platform happens primarily over a websocket, the SDK takes responsibility for maintaining this
connection and in the event of a connection loss it will attempt to reconnect.
The SDK will do this by default, no additional configuration or intervention is required.

While disconnected, any requests will be queued up and will execute when the connection is re-established.
The SDK will also attempt to recreate any subscriptions, including the default subscription.

If the auth token has become invalid during the time in which the connection was down, the SDK will attempt to
generate a new one based on the [Auth Token Process](#brand-auth-token-process).

Sometimes there could be issues with conversations being lost or stuck after reconnection. If you encounter this, set `unsubscribeFromOutdatedConversationsOnReconnect`
flag to `false` when you create the connection.

```javascript
const connection = lpm.createConnection({
    appId: 'quick_start',
    accountId,
    userType: lpm.UserType.BRAND,
    authData,
    unsubscribeFromOutdatedConversationsOnReconnect: false
});
```

#### Automatically mark bot as offline upon disconnect

By default, when a bot disconnects, the corresponding agent user stays online. To set the agent user to offline when the bot disconnects, set `setAgentOfflineOnDisconnect` flag to true when you create the connection.

```javascript
const connection = lpm.createConnection({
    appId: 'quick_start',
    accountId,
    userType: lpm.UserType.BRAND,
    authData,
    setAgentOfflineOnDisconnect: true
});
```
---

### Brand Authentication

#### Different authentication mechanisms

The SDK supports several authenticating mechanisms depending on the credentials provided using `authData`. For oAuth1, 
`authData` is expected to have the following format:

```javascript
const authData = {
    username: 'botName123',
    appKey: 'appKey',
    secret: 'secret',
    accessToken: 'accessToken',
    accessTokenSecret: 'accessTokenSecret'
}
```

If you have oAuth2 credentials, you can either use oAuth2 over oAuth1 or just oAuth2. For oAuth2 over oAuth1, the
`authData` looks as follows:

```javascript
const authData = {
  "username": "username",
  "appKey": "6YxG183r123456",
  "secret": "zQ1wIzypH123456",
  "accessToken": "hint",
  "accessTokenSecret": "hint"
}
```

The credentials themselves are provided through the `authData` property in the same way as oAuth1 credentials. The 
difference being that the values for `accessToken` and `accessTokenSecret` **MUST** be `"hint"` since they don't exist
for oAuth2. For just oAuth2, the `authData` looks as follows:

```javascript
const authData = {
  "oauth2": {
    "username": "username",
    "client_id": "6YxG183r123456",
    "client_secret": "zQ1wIzypH123456"
  }
}
```

If you provide oAuth1 and oAuth2 credentials, the oAuth2 credentials takes precedence. If you use oAuth2, authentication 
with [existing bearer token](#brand-authentication-with-existing-bearer-token) is not supported and will result in an 
`error` emit. If you want to use oAuth2 with an existing bearer token please use oAuth2 over oAuth1.

#### Brand Authentication Token Process

For brand connections, the SDK will use the provided `authData` to create a bearer token by making a request to an internal 
service called `agentVep`. This token is used to authenticate with the Messaging Platform when making a websocket connection 
or a rest api request. To guarantee a valid token for every request, there are different mechanisms in place for oAuth1 
and oAuth2. 

For oAuth1, a refresh request must be made once in every ten minutes back to `agentVep` so that the token stays valid. 
The SDK requests a refresh every four minutes by default. For oAuth2, a token expires after 60 minutes and cannot be 
refreshed. The SDK requests a new oAuth2 token every 30 minutes and stores it internally.

If a token becomes invalid for any reason, the SDK will automatically attempt to create a new one. In general, any
websockets established will not lose connection if their token becomes invalid, so there is no risk of service
interruption, but the new token will be required before any new http requests can be made.

If an application creates another token for the same user, the first token will become invalid. So it is therefore
important that applications do not create two connections with the same authData, this will cause them to
continually generate new tokens and put a strain on LivePerson's services. To share the same authentication information 
across multiple connections, please refer to [Brand Authentication using TokenMaintainer](#brand-authentication-using-tokenmaintainer).

If an application needs to use a connection's token to make http requests to other LivePerson services that are not
supported directly by the SDK, you can access the bearer token with the following method after the `connect()`
finishes: `await connection.getToken()`

---

#### Brand Authentication with existing bearer token

<div style="background-color: #f8d7da; color: #721c24; padding: 10px; border-radius: 5px; border: 1px solid #f5c6cb;">
  <strong>Notice:</strong> This mechanism only works with oAuth1 or oAuth2 over oAuth1 authentication. If you have oAuth2 
  credentials or session information from an oAuth2 connection, this authentication mechanism is not supported.
</div>
<br>

If you are already authenticated with agentVep and have a token at hand, you can use it with the SDK to establish an
authenticated brand connection. Add your bearer token to `authAgentSessionData` when setting up a  connection. If you
provide both the `authAgentSessionData` and `authData` an error is thrown. The format looks as follows:

```javascript
const authAgentSessionData = {
    token: 'your-bearer-token',
    userPid: 'uuid-of-the-user',
    csrf: 'csrf-key',
    sessionId: 'session-id'
}
```
Once this is set, the SDK will utilize the provided token for any subsequent brand requests. Token refresh will be managed
automatically by the SDK. Here's an example of how to set up the connection when using `authAgentSessionData`:

```javascript
const connection = lpm.createConnection({
    appId: 'quick_start',
    accountId,
    userType: lpm.UserType.BRAND,
    authAgentSessionData,
});
```

This ensures proper usage of the `authAgentSessionData` parameter and avoid errors when establishing connections.

---

#### Brand Authentication using TokenMaintainer

To share the same authentication information across multiple connections, pass the same `TokenMaintainer` instance to
each connection. The `TokenMaintainer` handles token refreshes.

```javascript
const accountId = '1234';   
const authData = { // This can also be oAuth2 credentials
    username: "username",
    appKey: "app-key",
    secret: "secret",
    accessToken: "accessToken",
    accessTokenSecret: "accessTokenSecret",
};

const tokenMaintainer = new TokenMaintainer({accountId, authData});

const connection = lpm.createConnection({
    appId: 'sharedAuthentication',
    application
    accountId,
    userType: lpm.UserType.BRAND,
    tokenMaintainer
});

const connection2 = lpm.createConnection({
    appId: 'sharedAuthentication2',
    accountId,
    userType: lpm.UserType.BRAND,
    tokenMaintainer
});

const connection3 = lpm.createConnection({
    appId: 'sharedAuthentication3',
    accountId,
    userType: lpm.UserType.BRAND,
    tokenMaintainer
});

await connection.open();
await connection2.open();
await connection3.open();
```

The `TokenMaintainer` provides several events you can listen to:

- The `error` event is triggered whenever an error occurs during refresh.
- The `token-invalid` event is triggered when the token becomes invalid.
- The `refresh-token` event is triggered when the token is refreshed.
- The `token-regenerated` event is triggered when the token is successfully regenerated.

To monitor the requests and responses between the server and the SDK, listen to the `getAgentToken#request` and
`getAgentToken#response` events. These events are emitted on all connections sharing the same token maintainer.

```javascript
tokenMaintainer
    .on('error', err => {...})
    .on('token-invalid', source => {...})
    .on('refresh-token', () => {...})
    .on('token-regenerated', info => {...})
    .on('getAgentToken#request', info => {...})
    .on('getAgentToken#response', info => {...});

connection
    .on('error', err => {...})
    .on('token-invalid', source => {...})
    .on('refresh-token', () => {...})
    .on('token-regenerated', info => {...})
    .on('getAgentToken#request', info => {...})
    .on('getAgentToken#response', info => {...});

connection2
    .on('error', err => {...})
    .on('token-invalid', source => {...})
    .on('refresh-token', () => {...})
    .on('token-regenerated', info => {...})
    .on('getAgentToken#request', info => {...})
    .on('getAgentToken#response', info => {...});

connection3
    .on('error', err => {...})
    .on('token-invalid', source => {...})
    .on('refresh-token', () => {...})
    .on('token-regenerated', info => {...})
    .on('getAgentToken#request', info => {...})
    .on('getAgentToken#response', info => {...});
```
---

### Continuing an anonymous user session between two connections

It is common for users of a web application to refresh their browser, or to close their browser and return to the site at a later time.
In these situations, the expectation is that the user resumes the same conversation.
However, from an application stand point, there is no way to preserve objects between page refreshes, much less between separate browser processes.

Instead, the solution is to save the JWT generated by the initial connection and give it to the subsequent connections. They can then use this JWT to connect, rather than generate their own new JWTs.
This will cause the Messaging Platform to recognize them as the same consumer, and give them access to the existing conversations for that consumer.

1. Once the initial connection is open, get the jwt by calling `await connection.getToken()`
2. Store that token in local storage or a cookie
3. Pass the token back in to `createConnection` as `token`

Any existing conversations will automatically be loaded up into the SDK and emitted as a `conversation` event.
They will also be available through `connection._conversations` which is a `Map`.
To ensure that that conversation is loaded by the time `await connection.open()` is resolved, you can pass `waitForReady: true` to `createConnection`.
This will cause `open` to only resolve once all conversations have been retrieved.

```javascript
// open the initial connection
const connection1 = Connection.createConnection({
    userType: UserType.CONSUMER,
    appId: 'quick_start',
    accountId: '123456789'
});
await connection1.open();

// get and store the token
const token = await connection1.getToken();

// create a conv so we can resume it in the 2nd connection
const conversation1 = await connection.createConversation();
await conversation1.sendMessage("hello");

// close connection
await connection1.close();

// open a second connection using the same token
const connection2 = Connection.createConnection({
    userType: UserType.CONSUMER,
    appId: 'quick_start',
    accountId: '123456789',
    token,
    waitForReady: true
});
await connection2.open();

// retrieve the conversation and close it
const conversation2 = Array.from(connection2._conversations.values())[0];
await conversation2.close();

// close connection
await connection2.close();
```

---

### Client Properties

ClientProperties is an object that contains information about the client that a consumer uses to connect to Messaging Platform.
This includes not only device and browser information, but also information about the specific messaging features
supported by the particular UI client they are connected through.

Example:

```javascript
const clientProperties = lpm.createClientProperties({
    deviceFamily: lpm.DeviceFamily.DESKTOP,
    deviceManufacturer: 'Apple',
    deviceModel: 'MacBook Pro',
    os: lpm.OS.OSX,
    osName: 'macOS',
    osVersion: '11.6.8',
    ipAddress: '127.0.0.1',
    browser: 'CHROME',
    browserVersion: '47.0',
    timeZone: 'America/Los_Angeles',
    features: [lpm.Features.AUTO_MESSAGES, lpm.Features.PHOTO_SHARING]
});

const consumerConnection = lpm.createConnection({
    appId: 'quick_start',
    accountId: '123456',
    userType: UserType.CONSUMER,
    clientProperties
});

await consumerConnection.open();
```

---

### Conversation Context and Campaign Info

To create a conversation with a given context and or campaign, use the following syntax.
Keep in mind that client properties should be sent during connection creation.

<!-- TODO: add other context types -->
<!-- TODO: show how to retrieve context as bot -->

```javascript
const context = {
    type: lpm.ConversationContextType.SHARK,
    lang: 'en-US',
    visitorId: '',
    sessionId: '',
    interactionContextId: '2'
};

const campaignInfo = {
    campaignId: '111',
    engagementId: '222'
};

const conversation = await connection.createConversation({context, campaignInfo});
```

---

### Consumer Auth Flow

By default, consumer connections will be made in "anonymous" mode.
Some customers have set up [Consumer Authentication](https://developers.liveperson.com/consumer-authentication-introduction.html) for their account.
To establish an authenticated consumer connection, your application must provide the `useAuthenticatedConnection` parameter along with a JWT (JSON Web Token).
This combination of `useAuthenticatedConnection` and JWT prompts the service to retrieve all connectors configured for the brand. Based on the issuer, it matches the connector ID to generate a token, facilitating authenticated consumer access.
Before establishing a connection, the brand must undertake an additional step to generate the token, which will be received via a callback. This token should then be added as a parameter to the createConnection function.
It's worth noting that the JWT generated by the brand's configured authentication service has an expiration time. Hence, the brand should supply a new token once the current one expires, if necessary.

Migration from the old structure to the new one includes deleting usage of those three methods: `createJwtClaimsSet,createCustomerInfoSde, createPersonalInfoSde` and start using the corresponding example described here.

```javascript
const connection = lpm.createConnection({
    appId: 'quick_start',
    appVersion: '1.6.2',
    accountId: '123456',
    userType: UserType.CONSUMER,
    useAuthenticatedConnection: true,
    jwt: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
});
```

Another type of consumer authentication is unauth connection. By providing `useUnauthenticatedConnection` to the createConnection function the system will know to use
unauth connector if such is configured for the brand

```javascript
const connection = lpm.createConnection({
    appId: 'quick_start',
    appVersion: '1.6.2',
    accountId: '123456',
    userType: UserType.CONSUMER,
    useUnauthenticatedConnection: true,
});
```

---

### Getting a User Profile

In order to retrieve authenticated consumer data or an agent's profile, call `getUserProfile`, which is available on any conversation participant.

```javascript
const consumerProfile = await conversation.consumer.getUserProfile();
const agentProfile = await conversation.assignedAgent.getUserProfile();
```

Alternatively, you can access all of the participants of a dialog at `conversation.openDialog.participants`. Note that consumers can only call getUserProfile on their own participant object; they cannot access agent or bot profiles.

---

### Set User Profile

To set a user profile outside of using the consumer auth flow, you can use `setUserProfile`.
Most of these fields are optional, and it is very common to call this function with just `firstName`, `lastName`, and `nickName` alone.

```javascript
const data = {
    "firstName": "1",
    "lastName": "2",
    "nickName": "3",
    "userId": "",
    "avatarUrl": "",
    "role": "",
    "backgndImgUri": "",
    "description": "",
    "privateData": {
        "mobileNum": "",
        "mail": "",
        "pushNotificationData": {
            "serviceName": "",
            "certName": "",
            "token": ""
        }
    },
    "claims": {
        "lp_sdes": [
            customerInfoSde,
            personalInfoSde
        ]
    }
};

await connection.setUserProfile(data);
```

---

### Consumer Step Up

Consumer stepup is a process by which an unauthenticated consumer connection is "stepped up" to be an authenticated connection.
First, create an **unauthenticated** consumer connection by providing the configuration `useUnauthenticatedConnection` to determine that we will use un auth connector and then opening the connection:

```javascript
const connection = lpm.createConnection({
    appId: 'quick_start',
    appVersion: '1.6.2',
    accountId: '123456',
    userType: UserType.CONSUMER,
    useUnauthenticatedConnection: true
});
await connection.open();
```

The consumer can create a conversation as usual. When the consumer logs in and you are ready to upgrade the connection to Messaging Platform, call the stepUp function, passing the jwt.
This will get an authenticated JWT, then disconnect and reconnect using the new JWT.
It will then issue a "takeover" command for any existing open conversation, so that the authenticated consumer takes ownership of the conversation, thereby completing the stepUp.

```javascript
await connection.stepUp(jwt);
```
---

### Message statistics
```javascript

const userData = await connection.getMessageStatisticsForUser();
const brandData = await connection.getMessageStatisticsForBrand();

```

That is what getMessageStatisticsForUser/getMessageStatisticsForBrand data returns when you call it.
```
{
    active: 0,
    pending: 0,
    unassigned: 0,
    overdue: 0,
    soonToOverdue: 0
}
```
---

### Using proxies

You can configure the SDK to redirect all requests to an HTTP proxy. Once you imported the messaging SDK, use the
`configureProxy` method to pass your proxy configuration. To remove the proxy, call `removeProxy`. When you configure the
proxy before establishing the connection, all requests are proxied. However, you can configure the proxy at any time you
want. The configuration structure looks as follows:

```
{
    host: string,
    port: number,
    protocol: string | undefined,
    path: string | undefined,
    auth: {
      username: string | number,
      password: string | number,
    } | undefined
}
```

The host can be a name or an address. The port refers to the proxy port. The protocol can either be https or http. It
defaults to http. The optional path is added to the URL. The optional `auth` object carries the `username` and `password`
for HTTP basic authentication. Path and host should not end with a `/`. Here is a usage example:

```javascript

const lpm = require("lp-messaging-sdk");

const proxy = {
    host: 'yourHost.com',
    port: 8080,
    protocol: 'http',
    path: 'user/1',
    auth: {
      username: 'username',
      password: 'password',
    }
};

lpm.configureProxy(proxy);

const connection = lpm.createConnection({
    appId: 'quick_start', // TODO: please change to something unique to your application
    accountId: '12345678',
    userType: lpm.UserType.BRAND,
});
```

If you want to remove an existing proxy you can do that by calling `removeProxy`;

```javascript
const lpm = require("lp-messaging-sdk");

lpm.removeProxy();

```

---

### Response Timeout

In order to provide you a way of manual configuration of what will be the response timeout instead of using the default one, we are providing `responseTimeout` option for your application.
`responseTimeout` should only contain digits `0-9`.
The value should be in milliseconds.
`responseTimeout` should be provided to the createConnection function.

```javascript
const consumerConnection = lpm.createConnection({
    appId: 'quick_start',
    appVersion: '1.12.2',
    accountId: '123456',
    userType: UserType.BRAND,
    responseTimeout: 10000
});
```

### Combined conversation handling

Conversations can be accepted on the ring and/or being awaited on the connection. If you configure both options, it
can happen, that your code will process a conversation twice. To ensure that a conversation is processed once, 
coordination between the callbacks of the `ring` and `conversation` events is necessary. The SDK facilitates this by 
using a common callback for combined conversation handling. For example, when the 
[conversation is accepted on the ring](#accepting-conversation-on-the-ring), the callback will be triggered either when
the `conversation` event was received on the connection or when the conversation could be resolved on the `ring` event 
through `ring.accept()`.

The callback can be configured using the `combinedConversationHandling` option.
```javascript
const consumerConnection = lpm.createConnection({
  appId: 'quick_start',
  appVersion: '1.12.2',
  accountId: '123456',
  userType: UserType.BRAND,
  combinedConversationHandling: {
    callback: brandImplementationFunctionToBeExecuted,
    combinedConversationHandlingOptions: {
      max: 10000,
      ttl: 1000 * 60 * 60 * 24,
    }
  }
});

async function brandImplementationFunctionToBeExecuted(conversation) {
    // Add code here
}
````
`combinedConversationHandling` contains the `callback` and `combinedConversationHandlingOptions`. `callback` is the 
function that will be executed when the `ring` is accepted or there is a `conversation` event. It must take a `conversation`
as parameter.`combinedConversationHandlingOptions` contains the configuration options as documented for 
https://www.npmjs.com/package/lru-cache. At least one option of max, ttl, or maxSize is required. If there are no options, 
the configuration defaults to 10.000 conversations for 1 day. When a conversation is evicted, then the callback will be 
called again if a ring for the conversation is accepted and/or the conversation is not yet known to the application. 
Conversations, will be removed from the cache once they ended.

For a more elaborated example, look into third party bot with [combined conversation handling](examples.md#third-party-bot-with-callback-configured).

---


---

## Features Not yet supported

* Agent State Subscriptions

* Conversation/Dialog level metadata (Message Metadata is available)

* SendAPI
