
Security News
Vite Releases Technical Preview of Rolldown-Vite, a Rust-Based Bundler
Vite releases Rolldown-Vite, a Rust-based bundler preview offering faster builds and lower memory usage as a drop-in replacement for Vite.
@servisbot/conversation-runtime
Advanced tools
Runtime SDK for Interacting with ServisBOT conversations
Runtime SDK for Interacting with ServisBOT conversations
npm install @servisbot/conversation-runtime
if you are looking for synchronous mode see below
import { ConversationConnector, ConversationChannelTypes, IngressEventFactory } from '@servisbot/conversation-runtime';
const organization = 'acme';
const apiKey = '<servisbot-conversation-api-key>'
const endpointAddress = 'acme-MyBot';
// can be "eu-1", "us1" or "usccif1"
const sbRegion = 'us1';
const logger = console;
const conversationConnector = await ConversationConnector.createConnector({
channelType: ConversationChannelTypes.OnDemandChannelRestWs,
apiKey,
endpointAddress,
logger,
sbRegion,
organization,
});
// setup conversation event listeners before creating the conversation
conversationConnector.on('message', (msg) => console.log('MESSAGE', msg));
conversationConnector.on('error', (err) => console.log('ERROR', err));
await conversationConnector.createConversation({
engagementAdapter: 'L2', engagementAdapterVersion: '1.0.0'
});
const message = IngressEventFactory.createMessage({ message: 'hello world' })
await conversationConnector.send(message);
Note
await conversationConnector.close();
const { ConversationConnector } = require('@servisbot/conversation-runtime'); // es5
import { ConversationConnector } from '@servisbot/conversation-runtime'; // es6
import { ConversationConnector, ConversationChannelTypes } from '@servisbot/conversation-runtime';
const apiKey = 'conversation-api-key';
const endpointAddress = 'acme-MyBot';
const context = { lang: 'en' };
const sbRegion = 'eu-1';
const organization = 'acme';
const customerReference = 'customer-123';
const regionalEndpoints = [
{ region: 'venus-eu-west-1.eu-1.servisbot.com' },
{ region: 'venus-eu-east-1.eu-1.servisbot.com' },
]
const conversationConnector = await ConversationConnector.createConnector({
channelType: ConversationChannelTypes.OnDemandChannelRestWs,
apiKey,
endpointAddress,
context,
logger: console,
sbRegion,
organization,
regionalEndpoints, // optional
customerReference // optional
});
eu-private-3
[
{ region: 'venus-eu-west-1.eu-private-3.servisbot.com' }
]
eu-private-1
[
{ region: 'venus-eu-west-1.eu-private-1.servisbot.com' }
]
eu-1
[
{ region: 'venus-eu-west-1.eu-1.servisbot.com' },
{ region: 'venus-eu-central-1.eu-1.servisbot.com' }
]
us1
OR us-1
(some clients pass us-1 instead of us1)[
{ region: 'venus-us-east-1.us1.servisbot.com' },
{ region: 'venus-us-east-2.us1.servisbot.com' }
]
usscif1
[
{ region: 'venus-us-west-1.usscif1.servisbot.com' }
]
on
function on the created connector.connector.on('message', (data) => console.log(data));
connector.on('error', (err) => console.log(err));
TimelineMessage
the connector will emit a message
event the full message object passed to the event listener.There are two types of notifications which can be emitted from the runtime:
HostNotification
events.Private
Host Notification events.on
function on the ConversationConnector
instance.connector.on('HostNotification', (hostNotificationEvent) => console.log(hostNotificationEvent));
Private Host Notification's contents.notification
attribute are prefixed with SB:::
.
This is the list of private host notifications which can be emitted from the runtime:
SB:::UserInputDisabled
SB:::UserInputEnabled
SB:::ValidationPassed
SB:::ValidationFailed
SB:::ProcessingPassed
SB:::ProcessingFailed
SB:::FileProcessorPipelinePassed
SB:::FileProcessorPipelineFailed
SB:::ResetConversation
SB:::Typing
SB:::MessengerOpen
SB:::MessengerClose
SB:::MessengerPopup
SB:::MessengerNavigate
SB:::MessengerStyle
SB:::UserInputNumericEnabled
SB:::UserInputNumericDisabled
on
function on the ConversationConnector
instance, and specifying one of the private host notification event names listed above.connector.on('<PRIVATE_HOST_NOTIFICATION_NAME_GOES_HERE>', () => console.log('Received private host notification'));
connector.on('SB:::Typing', (seconds) => console.log(seconds));
connector.on('SB:::ProcessingPassed', docId => console.log(docId));
connector.on('SB:::ProcessingFailed', docId => console.log(docId));
connector.on('SB:::ValidationPassed', docId => console.log(docId));
connector.on('SB:::ValidationFailed', docId => console.log(docId));
connector.on('SB:::FileProcessorPipelinePassed', context => console.log(context));
connector.on('SB:::FileProcessorPipelineFailed', context => console.log(context));
connector.on('SB:::MessengerPopup', seconds => console.log(seconds));
connector.on('SB:::MessengerNavigate', url => console.log(url));
connector.on('SB:::MessengerStyle', data => console.log(data));
connector.on('ConversationRefreshed', (refreshedConversation) => {
const newAuthToken = refreshedConversation.getAuthToken();
const newRefreshToken = refreshedConversation.getRefreshToken();
console.log(newRefreshToken, newRefreshToken);
})
refreshedConversation
passed to the callback in the event above is an instance of the Conversation
class within the conversation-runtime.connector.on('ConversationExpired', () => {
console.log('ConversationExpired event');
})
error
event, and pass the error instance to the event listener.SecureSession
etc.TimelineMessage
or an Error
, it will emit an event using the event's type
attribute and pass the full event to the event listener.SecureSession
event, the event will look like the following{
"organizationId": "acme",
"id": "aa8a6b64-2565-43ff-ab9d-007ebb0faa0b",
"conversationId": "conv-APD491fxU7xaYWU7M-CuU",
"identityId": "acme:::SYAGZio7T1T0_OAVEmdiC",
"sessionId": "uZ0790y6WKvnrvTdaRm5_",
"contentType": "state",
"contents": {
"state": "disabled"
},
"source": "server",
"type": "SecureSession",
"token": "SECURESESSION■conv-APD491fxU7xaYWU7M-CuU■■aa8a6b64-2565-43ff-ab9d-007ebb0faa0b"
}
The event's type
attribute will be used to emit the event, so this will result in a SecureSession
event being emitted with the payload shown above.
Conversation Event Handlers should be added before calling createConversation
or resumeConversation
on the connector. If you do not attach the handlers before calling createConversation
or resumeConversation
you risk missing events being emitted.
createConversation
function on the connector.const parameters = { engagementAdapter: 'L2', engagementAdapterVersion: '1.2.3'};
const conversation = await connector.createConversation(parameters);
createConversation
function will be passed along to the channel which is being used.Conversation
instance - see the class for more details.ReadyForConversation
on the server.NOTE
createConversation
is made, an error will be thrown.import { ConversationErrorCodes } from '@servisbot/conversation-runtime';
try {
const parameters = { engagementAdapter: 'L2', engagementAdapterVersion: '1.2.3'};
await connector.createConversation(parameters);
// second call to "createConversation" will throw as the connector already has a conversation.
await connector.createConversation(parameters);
} catch(err) {
if (err.code === ConversationErrorCodes.CONVERSATION_ALREADY_INITIALISED) {
console.log(err.message);
}
// unexpected error has occurred
throw err;
}
ConversationConnector
and create the conversation using the new connector.resumeConversation
function on the connector.import { Conversation } from '@servisbot/conversation-runtime';
const conversation = new Conversation({
authToken: 'some-token',
refreshToken: 'refresh-token',
conversationId: 'some-conversation-id',
hasActivity: true
});
const parameters = {
engagementAdapter: 'L2',
engagementAdapterVersion: '1.2.3',
conversation
};
const conversationResumeResponse = await connector.resumeConversation(parameters);
resumeConversation
function will be passed along to the channel which is being used.ReadyForConversation
endpoint.A ConversationError
can be thrown from a resumeConversation
call in the following cases:
A ConversationError
will be thrown with a code and a message. The code can be inspected within a try/catch
using the ConversationErrorCodes
.
import { ConversationErrorCodes } from '@servisbot/conversation-runtime';
try {
const conversationResumeResponse = await connector.resumeConversation(parameters);
} catch (_err) {
if (_err.code === ConversationErrorCodes.CONVERSATION_EXPIRED) {
console.log("Conversation has expired");
}
if(_err.code === ConversationErrorCodes.UNAUTHORIZED) {
console.log("Auth tokens are invalid")
}
// unexpected error has occurred
return {};
}
Error
will be thrown.You can send events by using the send
function on the conversation connector.
Depending on the runtime mode your running in, the responses will be in one of two places.
For OnDemandChannelRestWs, once an event is sent successfully it will be emitted on the conversation event emitter so that clients can acknowledge that an event was sent successfully.
For OnDemandChannelRestSync, once an event is sent successfully you will get a response, this response will contain any response messages from your the bot triggered by the message that was sent.
const sendResult = await conversationConnector.send(message);
// calling getMessages() will get return the response messages from the bot if there are any.
sendResult.getMessages().forEach(message => {
// calling getRawData() will give you the raw message payload
console.log('MESSAGE', message.getRawData())
})
id
- the event id, defaults to a v4 uuid if the provided value is not a string or is not a valid v4 uuid.correlationId
- the correlation id used to trace the event through the system, defaults to a v4 uuid if the provided value is not a string or is not a valid v4 uuid.import { IngressEventFactory } from '@servisbot/conversation-runtime';
const id = uuidV4(); // if not provided it will default to a v4 uuid
const correlationId = uuidV4(); // if not provided it will default to a v4 uuid
const message = 'hello world';
const message = IngressEventFactory.createMessage({ message, id, correlationId });
await connector.send(message);
import { IngressEventFactory } from '@servisbot/conversation-runtime';
const id = uuidV4(); // if not provided it will default to a v4 uuid
const correlationId = uuidV4(); // if not provided it will default to a v4 uuid
const source = {
eventType: 'TimelineMessage',
timestamp: 1647959518700,
id: 'jmfuvLOVT-',
conversationId: 'conv-kTKya6Oarb8lw2qUAIQae'
};
const interaction = {
action: 'listItemInteraction',
value: { id: '1', title: 'Item one' },
timestamp: 1647959522136
};
const markupInteractionEvent = IngressEventFactory.createMarkupInteraction({
source, interaction, id, correlationId
});
await connector.send(markupInteractionEvent);
import { IngressEventFactory } from '@servisbot/conversation-runtime';
const id = uuidV4(); // if not provided it will default to a v4 uuid
const correlationId = uuidV4(); // if not provided it will default to a v4 uuid
const body = { // optional
name: 'test-user'
};
const alias = 'page-event-alias';
const pageEvent = IngressEventFactory.createPageEvent({
alias, body, id, correlationId
});
await connector.send(pageEvent);
import { VendUserDocumentRequest } from '@servisbot/conversation-runtime';
const params = {
documentLabel: 'my-document', // required
metadata: { FileType: 'png', CustomerReference: 'cust-123' }, // optional, defaults to {}
additionalPathwayPath: '/some/additional/path' // optional
}
const vendUserDocumentRequest = new VendUserDocumentRequest(params);
const secureFileUploadManager = connector.getSecureFileUploadManager();
const response = await secureFileUploadManager.vendUserDocument(vendUserDocumentRequest);
const { url: urlForUploadingDocument, docId } = response;
console.log('Url for Uploading Document', url);
console.log('Document Id:', docId);
import { CreateSecureInputRequest } from '@servisbot/conversation-runtime';
const params = {
enigmaUrl: 'https://enigma.com',
jwt: 'some-jwt-token',
input: 'this is some sensitive input',
ttl: 124242 // optional, time to live in seconds
}
const createSecureInputRequest = new CreateSecureInputRequest(params);
const secureInputManager = connector.getSecureInputManager();
const secureInputResponse = await secureInputManager.createSecureInput(createSecureInputRequest);
if(secureInputResponse.isOk()) {
const secureInputSrn = secureInputResponse.getSrn();
console.log(`Secure input srn: ${secureInputSrn}`);
} else {
const {message: errMessage} = secureInputResponse.getBody();
console.log(`${errMessage} with status code ${secureInputResponse.getStatusCode()}`);
}
createImpression
function via the web client manager which can be retrieved via the getWebClientManager
function on the conversation connector instance.const webClientManager = connector.getWebClientManager();
const impressionId = 'some-impression-id';
await webClientManager.createImpression({ impressionId });
createImpression
call by suppliying an instance of WebClientMetadata
to the createImpression
function call.WebClientMetadata
instance, a WebClientMetadataBuilder
can be used.WebClientMetadataBuilder
to ensure you construct valid metadata for the createImpression
function call.createImpression
is invoked with an invalid WebClientMetadata
the data will not be passed along in the request to the server.WebClientMetadata
instance using the WebClientMetadataBuilder
.
messengerState
- the current state of the messenger. Possible values are open
or closed
. The MessengerStates
enum can be used to specify the state to be used when constructing the WebClientMetadata
.createImpression
with an instance of WebClientMetadata
.import { WebClientMetadataBuilder, MessengerStates } from '@servisbot/conversation-runtime';
const webClientMetadata = new WebClientMetadataBuilder()
.withMessengerState(MessengerStates.OPEN)
.build();
const webClientManager = connector.getWebClientManager();
const impressionId = 'some-impression-id';
await webClientManager.createImpression({ impressionId, webClientMetadata });
createTranscript
function via the venus connector. It will use the current conversation and identity to request a transcript be generated and return a signed get url to that transcript.const transcript = await connector.createTranscript();
createConversation
you can do the followingconst conversation = connector.getConversation();
const conversationId = conversation.getId();
close
function on the conversationawait connector.close();
reconnecting
- this event is emitted to the client when the runtime receives a "close" event from the server, and the runtime is about to start attempting to reconnect to the web socket.reconnectSuccess
- this event is emitted to the client when the runtime is in a reconnecting
state, and manages to establish a successful "open" event from a new web socket connection.reconnectFailed
- this event is emitted to the client when the runtime is in a reconnecting
state, and hits the maximum number of reconnect attempts allowed.connector.on('reconnecting', () => console.log('Reconnecting'));
connector.on('reconnectFailed', () => console.log('Reconnecting Failed'));
connector.on('reconnectSuccess', () => console.log('Reconnecting Success'));
WebSocketOptions
to the create/resume functions on the conversation connector, see below for an example:import { WebSocketOptions } from '@servisbot/conversation-runtime';
const webSocketOptions = new WebSocketOptions({
maxReconnectAttempts: 3,
baseReconnectDelay: 500,
establishConnectionTimeout: 1000
});
const parameters = { engagementAdapter: 'L2', engagementAdapterVersion: '1.2.3', webSocketOptions};
const conversation = await connector.createConversation(parameters);
WebSocketOptions
class.
reconnectEnabled
- a boolean to toggle the reconnect behaviour on/off. Defaults to true
, it is recommended to leave this as true
for reliability reasons.maxReconnectAttempts
- an integer representing the maximum amount of reconnect attempts that should be made before emitting the reconnectFailed
event. This excludes the initial connection attempt.baseReconnectDelay
- The base number of milliseconds to use in the exponential back off for reconnect attempts. Defaults to 100 ms.establishConnectionTimeout
- an integer in milliseconds representing the maximum amount of time to wait for the websocket to receive an "open" event from the server before considering the connection attempt a failure.
baseReconnectDelay
value. Take the following example.baseReconnectDelay
is set to 20
milliseconds and the maxReconnectAttempts
is set to 3
and the establishConnectionTimeout
is set to 100
milliseconds, and we exhaust all retries, the timings will be as follows:
20
milliseconds, we will then wait 100
before considering this reconnect attempt a failure.40
milliseconds after considering the first attempt a failure, we will then wait another 100
milliseconds before considering this attempt a failure.80
milliseconds after considering the second attempt a failure, we will then wait another 100
milliseconds before considering this attempt a failure, we will then emit a reconnectFailed
error as we have hit the max reconnect attempts limit.440
milliseconds.20
, 40
and 80
were used to simplify the example above.maxFailedHealthChecks
- an integer representing the maximum number of health check failures in a row before considering the web socket unhealthymaxHealthCheckResponseTime
- an integer in milliseconds representing the maximum amount of time to wait for a "pong" response after sending a "ping" request before considering the health check a failure.healthCheckInterval
- an integer in milliseconds representing how long to wait between health checks.WebSocketOptions
are supplied the following defaults will be used:
reconnectEnabled
- true
maxReconnectAttempts
- 3
baseReconnectDelay
- 100
(milliseconds)establishConnectionTimeout
- 3000
(milliseconds)maxFailedHealthChecks
- 3
maxHealthCheckResponseTime
- 500
(milliseconds)healthCheckInterval
- 5000
(milliseconds) - 5 seconds3
seconds.100
milliseconds.maxFailedHealthChecks
option, the conversation-runtime will attempt to get a new web socket connection by starting the reconnect process.When using synchronous, all request are made synchronously. Each time you create a conversation or send in an event it will wait for the response including any messages related to that event. These are returned from the function call.
For more information on the event handlers you can register see here
Note that host notifications and messages will come back in the response from both the send() and createConversation() calls.
import { ConversationConnector, ConversationChannelTypes, IngressEventFactory } from '@servisbot/conversation-runtime';
const organization = 'acme';
const apiKey = '<servisbot-conversation-api-key>'
const endpointAddress = 'acme-MyBot';
// can be "eu-1", "us1" or "usccif1"
const sbRegion = 'us1';
// When using sync mode the connector should use the `OnDemandChannelRestSync` channel type from the ConversationChannelTypes
const conversationConnector = await ConversationConnector.createConnector({
channelType: ConversationChannelTypes.OnDemandChannelRestSync,
apiKey,
endpointAddress,
logger,
sbRegion,
organization,
});
// Setup conversation event listeners before creating the conversation
conversationConnector.on('ConversationRefreshed', (msg) => console.log('ConversationRefreshed', msg));
const conversation = await conversationConnector.createConversation({
engagementAdapter: 'L2', engagementAdapterVersion: '1.0.0'
});
// When a conversation is created using the synchronous channel, the conversation can contain welcome messages as part of the conversation instance
// You can call getWelcomeMessages() to get any messages from the result
const messages = conversation.getWelcomeMessages();
messages.forEach(message => {
// calling getRawData() will give you the raw message payload
console.log('MESSAGE', message.getRawData())
})
const message = IngressEventFactory.createMessage({ message: 'content' })
const sendResult = await conversationConnector.send(message);
// calling getMessages() will get any messages from response
sendResult.getMessages().forEach(message => {
// calling getRawData() will give you the raw message payload
console.log('MESSAGE', message.getRawData())
})
There is no continuous health checking (pinging) to the server when using the synchronous channel. This means the detection of a conversation expiring will not occur until the connector attempts to send a message or resume a conversation.
An error will be thrown if the server determines that the conversation has expired in both of the above cases.
FAQs
Runtime SDK for Interacting with ServisBOT conversations
The npm package @servisbot/conversation-runtime receives a total of 7 weekly downloads. As such, @servisbot/conversation-runtime popularity was classified as not popular.
We found that @servisbot/conversation-runtime demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Vite releases Rolldown-Vite, a Rust-based bundler preview offering faster builds and lower memory usage as a drop-in replacement for Vite.
Research
Security News
A malicious npm typosquat uses remote commands to silently delete entire project directories after a single mistyped install.
Research
Security News
Malicious PyPI package semantic-types steals Solana private keys via transitive dependency installs using monkey patching and blockchain exfiltration.