UNPKG

@ovotech/genesys-web-messaging-tester

Version:
310 lines (309 loc) 13.1 kB
"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;