@ovotech/genesys-web-messaging-tester
Version:
310 lines (309 loc) • 13.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Conversation = exports.TimeoutWaitingForDisconnectionError = exports.BotDisconnectedWaitingForResponseError = exports.TimeoutWaitingForResponseError = void 0;
class TimeoutWaitingForResponseError extends Error {
constructor(_timeoutInMs, _expectedResponse, _responsesReceived = []) {
super(TimeoutWaitingForResponseError.createFailureMessage(_timeoutInMs, _expectedResponse, _responsesReceived));
this._timeoutInMs = _timeoutInMs;
this._expectedResponse = _expectedResponse;
this._responsesReceived = _responsesReceived;
Object.setPrototypeOf(this, TimeoutWaitingForResponseError.prototype);
}
static createFailureMessage(timeoutInMs, expectedResponse, responsesReceived) {
if (responsesReceived.length === 0) {
return `Timed-out after ${timeoutInMs}ms waiting for a message that contained '${expectedResponse}'.
No messages were received.`;
}
else {
return `Timed-out after ${timeoutInMs}ms waiting for a message that contained '${expectedResponse}'
Received:
${responsesReceived.map((m) => ` - ${m.text}`).join('\n')}`;
}
}
get expectedResponse() {
return this._expectedResponse;
}
get responsesReceived() {
return this._responsesReceived;
}
get timeoutInMs() {
return this._timeoutInMs;
}
}
exports.TimeoutWaitingForResponseError = TimeoutWaitingForResponseError;
class BotDisconnectedWaitingForResponseError extends Error {
constructor(_expectedResponse, _responsesReceived = []) {
super(BotDisconnectedWaitingForResponseError.createFailureMessage(_expectedResponse, _responsesReceived));
this._expectedResponse = _expectedResponse;
this._responsesReceived = _responsesReceived;
Object.setPrototypeOf(this, BotDisconnectedWaitingForResponseError.prototype);
}
static createFailureMessage(expectedResponse, responsesReceived) {
if (responsesReceived.length === 0) {
return `Bot disconnected from the conversation whilst waiting a message that contained '${expectedResponse}'.
No messages were received before disconnection.`;
}
else {
return `Bot disconnected from the conversation whilst waiting a message that contained '${expectedResponse}'
Received before disconnection:
${responsesReceived.map((m) => ` - ${m.text}`).join('\n')}`;
}
}
get expectedResponse() {
return this._expectedResponse;
}
get responsesReceived() {
return this._responsesReceived;
}
}
exports.BotDisconnectedWaitingForResponseError = BotDisconnectedWaitingForResponseError;
class TimeoutWaitingForDisconnectionError extends Error {
constructor() {
super('Bot did not disconnect from the conversation within the timeout period');
Object.setPrototypeOf(this, TimeoutWaitingForDisconnectionError.prototype);
}
}
exports.TimeoutWaitingForDisconnectionError = TimeoutWaitingForDisconnectionError;
/**
* Provides an API to simplify sending and receiving messages in a Web Messenger
* session.
*
* ```typescript
* const convo = new Conversation(
* new WebMessengerGuestSession({
* deploymentId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
* region: 'xxxx.pure.cloud',
* }),
* );
*
* await convo.waitForConversationToStart();
* await convo.sendText('hi');
*
* await convo.waitForResponseContaining('Is this an example?');
*
* await convo.sendText('yes');
*
* const reply = await convo.waitForResponse();
* console.log(reply);
* ```
*/
class Conversation {
constructor(messengerSession, timeoutSet = setTimeout, timeoutClear = clearTimeout) {
this.messengerSession = messengerSession;
this.timeoutSet = timeoutSet;
this.timeoutClear = timeoutClear;
this.disconnectedByGenesys = false;
this.sessionStarted = false;
this.messengerSession.once('sessionStarted', () => (this.sessionStarted = true));
this.timeoutCompensation = messengerSession.messageDelayInMs;
const detectDisconnectFunc = (event) => {
if (event.body.direction === 'Outbound' &&
event.body.type === 'Event' &&
Conversation.containsDisconnectEvent(event.body)) {
this.disconnectedByGenesys = true;
this.messengerSession.off('structuredMessage', detectDisconnectFunc);
}
};
this.messengerSession.on('structuredMessage', detectDisconnectFunc);
}
static containsDisconnectEvent(event) {
return event.events.some((e) => e.eventType === 'Presence' && e.presence.type === 'Disconnect');
}
async waitForConversationToClose(timeoutInMs = 2000) {
return new Promise((resolve, reject) => {
if (this.disconnectedByGenesys) {
resolve();
return;
}
let timeout = undefined;
const checkMessage = (event) => {
if (event.body.direction === 'Outbound' &&
event.body.type === 'Event' &&
Conversation.containsDisconnectEvent(event.body)) {
if (timeout) {
this.timeoutClear(timeout);
}
resolve();
}
};
this.messengerSession.on('structuredMessage', checkMessage);
timeout = this.timeoutSet(() => {
this.messengerSession.off('structuredMessage', checkMessage);
reject(new TimeoutWaitingForDisconnectionError());
}, timeoutInMs);
});
}
/**
* Returns whether the conversation has been disconnected
*/
get isDisconnected() {
return this.disconnectedByGenesys;
}
/**
* Resolves when the conversation has started.
*
* Starting a conversation is an automatic process that happens in the
* background. This method allows you to wait for this process to finish.
*/
async waitForConversationToStart() {
if (this.sessionStarted) {
return this;
}
return new Promise((resolve) => {
this.messengerSession.once('sessionStarted', () => {
this.sessionStarted = true;
resolve(this);
});
});
}
/**
* Sends text to the conversation
* @param text Text containing at least one character
* @param delayInMs Delay in milliseconds between calling this method and the text being sent.
* Without a delay some messages are sent so quickly after the original message
* that Genesys Cloud doesn't acknowledge them.
* A delay of 0 will result in the text being sent immediately.
*/
async sendText(text, delayInMs = 2000) {
if (text.length === 0) {
throw new Error('Text cannot be empty');
}
if (this.disconnectedByGenesys) {
throw new Error('Cannot send text as conversation was disconnected by Genesys');
}
if (delayInMs > 0) {
return new Promise((resolve) => {
setTimeout(() => {
this.messengerSession.sendText(text);
resolve();
}, delayInMs + this.timeoutCompensation);
});
}
else {
this.messengerSession.sendText(text);
}
}
/**
* Resolves on the next response from the other participant in the conversation that contains text.
*
* If you want to wait for a specific message use {@link waitForResponseWithTextContaining}.
*/
async waitForResponseText() {
return new Promise((resolve) => {
this.messengerSession.on('structuredMessage', (event) => {
if ((event.body.type === 'Text' || event.body.type === 'Structured') &&
event.body.direction === 'Outbound') {
resolve(event.body.text);
}
});
});
}
/**
* Wait for all responses until there is a predefined amount of 'silence'.
*/
async waitForResponses(timeToWaitAfterLastMessageInMs = 2000) {
return new Promise((resolve) => {
const messages = [];
let waitingTimeout;
const func = (event) => {
if ((event.body.type === 'Text' || event.body.type === 'Structured') &&
event.body.direction === 'Outbound') {
messages.push(event.body.text);
if (waitingTimeout) {
this.timeoutClear(waitingTimeout);
}
waitingTimeout = this.timeoutSet(() => {
this.messengerSession.off('structuredMessage', func);
resolve(messages);
}, timeToWaitAfterLastMessageInMs + this.timeoutCompensation);
}
};
// Set Initial wait
waitingTimeout = this.timeoutSet(() => {
this.messengerSession.off('structuredMessage', func);
resolve(messages);
}, timeToWaitAfterLastMessageInMs + this.timeoutCompensation);
this.messengerSession.on('structuredMessage', func);
});
}
/**
* Resolves when a response is received that contains a specific piece of text.
* If no response is received that contains the text within the timeout period
* then an exception is thrown.
*
* Case-insensitive by default.
*
* If you want to wait for the next response, regardless of what it contains
* use {@link waitForResponseText}.
*/
async waitForResponseWithTextContaining(text, { timeoutInSeconds = 10, caseInsensitive = true, } = {}) {
return this.waitForResponseWithCheck({
check(messageText) {
const message = caseInsensitive ? messageText.toLocaleLowerCase() : messageText;
const expectedText = caseInsensitive ? text.toLocaleLowerCase() : text;
return message.includes(expectedText);
},
describeCheck() {
return text;
},
}, timeoutInSeconds);
}
/**
* Resolves when a response is received that matches a regular expression.
* If no response is received that matches the pattern within the timeout period
* then an exception is thrown.
*
* If you want to wait for the next response, regardless of what it contains
* use {@link waitForResponseText}.
*/
async waitForResponseWithTextMatchingPattern(pattern, { timeoutInSeconds = 10 } = {}) {
return this.waitForResponseWithCheck({
check(messageText) {
return new RegExp(pattern).test(messageText);
},
describeCheck() {
return pattern.toString();
},
}, timeoutInSeconds);
}
/**
* Resolves when a response is received that matches a check.
* If no response is received that the check matches within the timeout period
* then an exception is thrown.
*/
waitForResponseWithCheck(messageCheck, timeoutInSeconds) {
const timeoutInMs = timeoutInSeconds * 1000;
const messagesWithTextReceived = [];
return new Promise((resolve, reject) => {
let timeout = undefined;
const checkMessage = (event) => {
if (event.body.direction === 'Outbound' &&
event.body.type === 'Event' &&
Conversation.containsDisconnectEvent(event.body)) {
if (timeout) {
this.timeoutClear(timeout);
}
reject(new BotDisconnectedWaitingForResponseError(messageCheck.describeCheck(), messagesWithTextReceived));
}
if (event.body.type === 'Text' || event.body.type === 'Structured') {
if (event.body.direction === 'Outbound') {
messagesWithTextReceived.push(event.body);
if (messageCheck.check(event.body.text)) {
this.messengerSession.off('structuredMessage', checkMessage);
if (timeout) {
this.timeoutClear(timeout);
}
resolve(event.body.text);
}
}
}
};
this.messengerSession.on('structuredMessage', checkMessage);
timeout = this.timeoutSet(() => {
this.messengerSession.off('structuredMessage', checkMessage);
reject(new TimeoutWaitingForResponseError(timeoutInMs, messageCheck.describeCheck(), messagesWithTextReceived));
}, timeoutInMs + this.timeoutCompensation);
});
}
}
exports.Conversation = Conversation;