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.
npm install lp-messaging-sdkAn 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.
Messaging Platform SDK has replaced Messaging Agent SDK (aka Node Agent SDK) as the recommended method for interacting with Messaging Platform from a node.js application.
See the Feature Comparison for more information.
For information about converting a Node Agent SDK based project to use Messaging Platform SDK, see our Conversion Guide.
* How To Install
* Quick Start
* A simple consumer conversation
* A simple agent conversation listener bot
* Commonly Used Features
* Application Tracking
* Conversations
* Create Conversation
* Conversation Functions
* Join and Leave
* Transfer
* Close
* Resume conversation
* Get Latest Consumer Message
* Rings
* Agent Routing Tasks aka "Rings"
* Accepting conversation on the ring
* FAQ
* Conversation Events
* Conversation Event: close
* Conversation Event: transfer-skill
* Conversation Event: transfer-agent
* Conversation Event: back-to-queue
* Conversation Event: consumer-step-up
* Conversation Event: participant-added
* Conversation Event: participant-removed
* Messages
* Markdown in Messages
* Message Events
* Previous messages
* Message is sent by current user
* Loading all previous messages
* Message Metadata
* Message quick replies
* Error Handling
* File Sharing
* Upload a file from Node.js
* Download a file from Node.js
* Upload a file from browser
* Download a file in browser
* Advanced Topics
* Background Process Errors
* Arbitrary websocket requests
* Notification Events
* Notification Types
* Message Event Notification
* Conversation State Notification
* Routing Notification
* Notification order
* Subscriptions
* The default subscription
* Creating manual subscriptions
* Query properties
* Connection Maintenance
* Automatically mark bot as offline upon disconnect
* Brand Authentication
* Different authentication mechanisms
* Brand Authentication Token Process
* Brand Authentication with existing bearer token
* Brand Authentication using TokenMaintainer
* Continuing an anonymous user session between two connections
* Client Properties
* Conversation Context and Campaign Info
* Consumer Auth Flow
* Getting a User Profile
* Set User Profile
* Consumer Step Up
* Message statistics
* Using proxies
* Response Timeout
* Combined conversation handling
* Features Not yet supported
To install as a dependency, run the following from a terminal window:
npm install lp-messaging-sdk
``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();
`
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
$3
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
});
`---
$3
#### 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 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.
`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();
`---
$3
#### 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
Q: When a ring is accepted, can it be accepted by another agent?
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.
Q: Can two different rings share the same id?
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.
Q: How is the skillId of the ring determined?
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.
Q: What happens when an agent rejects a ring?
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.
$3
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 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
---
$3
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.`javascript
await conversation.sendMessage('view our docs #md#here#/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);
`---
$3
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})
}`---
$3
Accounts that have filesharing enabled
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
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.
#### 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
`#### 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
$3
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);
});
`---
$3
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);
`---
$3
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)});
})
`---
$3
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, 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.
$3
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 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.$3
#### 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.
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.
Notice: If agentId is not provided - the bots will try to subscribe to all open conversation for the given account.
#### 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: ["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: ["OPEN", "CLOSE"] | Filters for conversations with a MAIN dialog in given state. Possible values: OPEN, CLOSE, LOCKED, CLOSING |
| query.stage | Array: ["OPEN", "CLOSE"] | Filters for conversations being in given stage. Possible values: OPEN, CLOSE, LOCKED, CLOSING |
| query.dialogType | Array: ["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"]
}
}
`---
$3
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.
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
});
`
---$3
#### 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 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.
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
Notice: 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.
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 => {...});
`
---$3
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 tokenAny 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();
`---
$3
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();
`---
$3
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.
`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});
`---
$3
By default, consumer connections will be made in "anonymous" mode.
Some customers have set up Consumer Authentication 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,
});
`---
$3
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.---
$3
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);
`---
$3
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);
`
---$3
`javascriptconst 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
}
`
---$3
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:`javascriptconst 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();
`---
$3
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
});
`$3
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, 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 For a more elaborated example, look into third party bot with combined conversation handling.
---
---
* Agent State Subscriptions
* Conversation/Dialog level metadata (Message Metadata is available)
* SendAPI