lp-messaging-sdk
v1.16.0
Published
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.
Downloads
641
Readme
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.
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.
Table of Contents
- How To Install
- Quick Start
- Commonly Used Features
- Advanced Topics
- Background Process Errors
- Arbitrary websocket requests
- Notification Events
- Notification Types
- Subscriptions
- Connection Maintenance
- Brand Authentication
- 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
- 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
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
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.
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".
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.
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.
await conversation.join(lpm.ParticipantRole.ASSIGNED_AGENT);
To leave a conversation, use the leave function.
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
.
// 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:
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:
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:
const conversationId = '123456789';
await connection.closeConversation(conversationId);
Resume conversation
To resume previously closed conversation you can use the resumeConversation function
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.
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
.
// 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.
// 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
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.
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.
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.
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.
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.
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:
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.
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.
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.
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:
// 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.
conversation.on('participant-removed', async (removedParticipant) => {
});
For a complete list of events emitted by the conversation object, see Events section of Conversation class
Messages
To send a message to a conversation there are several methods you can use:
// 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)
.
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).
// 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.
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.
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.
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
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.
// 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:
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.
// 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
.
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 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.
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.
// 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.
<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.
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.
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:
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.
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, 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.
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:
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:
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:
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.
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.
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:
{
"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.
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.
const connection = lpm.createConnection({
appId: 'quick_start',
accountId,
userType: lpm.UserType.BRAND,
authData,
unsubscribeFromOutdatedConversationsOnReconnect: false
});
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:
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:
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:
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
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:
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
:
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.
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.
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.
- Once the initial connection is open, get the jwt by calling
await connection.getToken()
- Store that token in local storage or a cookie
- Pass the token back in to
createConnection
astoken
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.
// 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:
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.
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 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.
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
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.
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.
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:
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.
await connection.stepUp(jwt);
Message statistics
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:
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
;
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.
const consumerConnection = lpm.createConnection({
appId: 'quick_start',
appVersion: '1.12.2',
accountId: '123456',
userType: UserType.BRAND,
responseTimeout: 10000
});
Features Not yet supported
Agent State Subscriptions
Conversation/Dialog level metadata (Message Metadata is available)
SendAPI