diff --git a/app.json b/app.json index 5a8fc5d..e14d25a 100644 --- a/app.json +++ b/app.json @@ -19,5 +19,5 @@ "IPostLivechatRoomClosed", "IUIKitLivechatInteractionHandler" ], - "commitHash": "69de13511e790a02257140867ae181511241c394" + "commitHash": "d036be146784ca79f84f0b2cae50f3cd518cf5a9" } \ No newline at end of file diff --git a/endpoints/IncomingEndpoint.ts b/endpoints/IncomingEndpoint.ts index a697c12..8f61db3 100644 --- a/endpoints/IncomingEndpoint.ts +++ b/endpoints/IncomingEndpoint.ts @@ -1,7 +1,7 @@ import { HttpStatusCode, IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; import { ApiEndpoint, IApiEndpointInfo, IApiRequest, IApiResponse } from '@rocket.chat/apps-engine/definition/api'; import { ILivechatRoom } from '@rocket.chat/apps-engine/definition/livechat'; -import { DialogflowRequestType, IDialogflowMessage } from '../enum/Dialogflow'; +import { CloseChatDescription, DialogflowRequestType, IDialogflowMessage } from '../enum/Dialogflow'; import { EndpointActionNames, IActionsEndpointContent } from '../enum/Endpoints'; import { Headers, Response } from '../enum/Http'; import { Logs } from '../enum/Logs'; @@ -52,7 +52,7 @@ export class IncomingEndpoint extends ApiEndpoint { const room = (await read.getRoomReader().getById(sessionId)) as ILivechatRoom; switch (action) { case EndpointActionNames.CLOSE_CHAT: - await closeChat(modify, read, sessionId); + await closeChat(modify, read, sessionId, this.app, CloseChatDescription.CLOSED_BY_API_ENDPOINT); break; case EndpointActionNames.HANDOVER: const { actionData: { targetDepartment = '' } = {} } = endpointContent; diff --git a/enum/Dialogflow.ts b/enum/Dialogflow.ts index 38b8fdf..1909230 100644 --- a/enum/Dialogflow.ts +++ b/enum/Dialogflow.ts @@ -148,3 +148,16 @@ export enum Message { CLOSED_BY_VISITOR = 'Closed by visitor', CUSTOMER_IDLE_TIMEOUT = 'customer_idle_timeout', } + +export enum CloseChatDescription { + CLOSED_BY_VISITOR = 'Closed by visitor', + CLOSED_BY_AGENT = 'Closed by DF agent', + CUSTOMER_IDLE_TIMEOUT = 'Customer Timeout', + INVALID_TARGET_DEPARTMENT = 'Escalation failed due to invalid target department', + SAME_TARGET_DEPARTMENT = 'Escalation failed due to same target department', + LIVEAGENT_BOT_OFFLINE_OR_DISABLED = 'Escalation failed due to liveagent bot offline or disabled', + NETWORK_OR_APP_ERROR = 'Escalation failed due to network or DF app error', + AGENT_UNASSIGNED = 'Agent is unassigned', + INVALID_EVENT_DATA = 'Invalid Dialogflow event data', + CLOSED_BY_API_ENDPOINT = 'Closed by incoming API/Endpoint', +} diff --git a/handler/ExecuteLivechatBlockActionHandler.ts b/handler/ExecuteLivechatBlockActionHandler.ts index 4239528..41033d3 100644 --- a/handler/ExecuteLivechatBlockActionHandler.ts +++ b/handler/ExecuteLivechatBlockActionHandler.ts @@ -6,6 +6,7 @@ import { UIKitIncomingInteractionContainerType } from '@rocket.chat/apps-engine/ import { IUser } from '@rocket.chat/apps-engine/definition/users'; import { AppSetting, DefaultMessage } from '../config/Settings'; import { ActionIds } from '../enum/ActionIds'; +import { CloseChatDescription } from '../enum/Dialogflow'; import { createLivechatMessage, createMessage, deleteAllActionBlocks } from '../lib/Message'; import { closeChat, performHandover } from '../lib/Room'; import { getLivechatAgentConfig } from '../lib/Settings'; @@ -64,7 +65,7 @@ export class ExecuteLivechatBlockActionHandler { break; case ActionIds.CLOSE_CHAT: - await closeChat(this.modify, this.read, rid); + await closeChat(this.modify, this.read, rid, this.app, CloseChatDescription.CLOSED_BY_VISITOR); break; default: await createLivechatMessage(this.app, rid, this.read, this.modify, { text: value }, visitor); diff --git a/handler/OnAgentUnassignedHandler.ts b/handler/OnAgentUnassignedHandler.ts index 4e0d654..c66b787 100644 --- a/handler/OnAgentUnassignedHandler.ts +++ b/handler/OnAgentUnassignedHandler.ts @@ -1,11 +1,12 @@ import { IModify, IRead } from '@rocket.chat/apps-engine/definition/accessors'; import { IApp } from '@rocket.chat/apps-engine/definition/IApp'; import { ILivechatEventContext, ILivechatRoom } from '@rocket.chat/apps-engine/definition/livechat'; -import { AppSetting, DefaultMessage } from '../config/Settings'; -import { IRoomCustomField } from '../enum/Dialogflow'; +import { AppSetting } from '../config/Settings'; +import { CloseChatDescription, IRoomCustomField } from '../enum/Dialogflow'; import { Logs } from '../enum/Logs'; import { removeBotTypingListener } from '../lib//BotTyping'; import { createMessage } from '../lib/Message'; +import { sendCloseChatMessage } from '../lib/Room'; import { cancelAllSessionMaintenanceJobForSession } from '../lib/Scheduler'; import { agentConfigExists, getLivechatAgentConfig } from '../lib/Settings'; @@ -28,7 +29,9 @@ export class OnAgentUnassignedHandler { await createMessage(livechatRoom.id, this.read, this.modify, { text: offlineMessage }, this.app); if (livechatRoom.isOpen) { - await closeChat(this.modify, this.read, rid); + await closeChat(this.modify, this.read, rid, this.app, CloseChatDescription.AGENT_UNASSIGNED); + } else { + await sendCloseChatMessage(this.read, this.modify, this.app, rid); } } @@ -36,7 +39,7 @@ export class OnAgentUnassignedHandler { } } -export const closeChat = async (modify: IModify, read: IRead, rid: string) => { +export const closeChat = async (modify: IModify, read: IRead, rid: string, app: IApp, closeChatDesc?: string) => { await cancelAllSessionMaintenanceJobForSession(modify, rid); const room = (await read.getRoomReader().getById(rid)) as ILivechatRoom; if (!room) { @@ -50,12 +53,12 @@ export const closeChat = async (modify: IModify, read: IRead, rid: string) => { const DialogflowBotUsername = room.servedBy.username; await removeBotTypingListener(modify, rid, DialogflowBotUsername); - const closeChatMessage = await getLivechatAgentConfig(read, rid, AppSetting.DialogflowCloseChatMessage); + await sendCloseChatMessage(read, modify, app, rid); const result = await modify .getUpdater() .getLivechatUpdater() - .closeRoom(room, closeChatMessage ? closeChatMessage : DefaultMessage.DEFAULT_DialogflowCloseChatMessage); + .closeRoom(room, closeChatDesc || ''); if (!result) { throw new Error(Logs.CLOSE_CHAT_REQUEST_FAILED_ERROR); } diff --git a/handler/PostMessageSentHandler.ts b/handler/PostMessageSentHandler.ts index 155c9ee..4133236 100644 --- a/handler/PostMessageSentHandler.ts +++ b/handler/PostMessageSentHandler.ts @@ -3,7 +3,7 @@ import { IApp } from '@rocket.chat/apps-engine/definition/IApp'; import { ILivechatMessage, ILivechatRoom } from '@rocket.chat/apps-engine/definition/livechat'; import { RoomType } from '@rocket.chat/apps-engine/definition/rooms'; import { AppSetting } from '../config/Settings'; -import { DialogflowRequestType, IDialogflowMessage, IDialogflowQuickReplies, LanguageCode, Message } from '../enum/Dialogflow'; +import { CloseChatDescription, DialogflowRequestType, IDialogflowMessage, IDialogflowQuickReplies, LanguageCode, Message } from '../enum/Dialogflow'; import { Logs } from '../enum/Logs'; import { botTypingListener, removeBotTypingListener } from '../lib//BotTyping'; @@ -72,7 +72,7 @@ export class PostMessageSentHandler { if (text === Message.CUSTOMER_IDLE_TIMEOUT) { await this.handleClosedByVisitor(rid, this.read); - await closeChat(this.modify, this.read, rid); + await closeChat(this.modify, this.read, rid, this.app, CloseChatDescription.CUSTOMER_IDLE_TIMEOUT); return; } diff --git a/lib/Room.ts b/lib/Room.ts index eb1f917..b226aae 100644 --- a/lib/Room.ts +++ b/lib/Room.ts @@ -1,10 +1,10 @@ -import { IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; +import { IModify, IRead } from '@rocket.chat/apps-engine/definition/accessors'; import { IApp } from '@rocket.chat/apps-engine/definition/IApp'; import { IDepartment, ILivechatRoom, ILivechatTransferData, IVisitor } from '@rocket.chat/apps-engine/definition/livechat'; import { IButtonElement } from '@rocket.chat/apps-engine/definition/uikit'; import { AppSetting, DefaultMessage } from '../config/Settings'; import { EventName } from '../enum/Analytics'; -import { IImageBlockExtended, IRoomCustomField } from '../enum/Dialogflow'; +import { CloseChatDescription, IImageBlockExtended, IRoomCustomField } from '../enum/Dialogflow'; import { Logs } from '../enum/Logs'; import { JobName } from '../enum/Scheduler'; import { getEventData } from '../lib/Analytics'; @@ -74,8 +74,7 @@ export const updateRoomLogData = async (rid: string, data: IRoomCustomField, rea } }; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export const closeChat = async (modify: IModify, read: IRead, rid: string, _persistence?: IPersistence) => { +export const closeChat = async (modify: IModify, read: IRead, rid: string, app: IApp, closeChatDesc?: string) => { const room: ILivechatRoom = (await read.getRoomReader().getById(rid)) as ILivechatRoom; if (!room) { throw new Error(Logs.INVALID_ROOM_ID); @@ -92,17 +91,23 @@ export const closeChat = async (modify: IModify, read: IRead, rid: string, _pers const DialogflowBotUsername = room.servedBy.username; await removeBotTypingListener(modify, rid, DialogflowBotUsername); - const closeChatMessage = await getLivechatAgentConfig(read, rid, AppSetting.DialogflowCloseChatMessage); - + await sendCloseChatMessage(read, modify, app, rid); const result = await modify .getUpdater() .getLivechatUpdater() - .closeRoom(room, closeChatMessage ? closeChatMessage : DefaultMessage.DEFAULT_DialogflowCloseChatMessage); + .closeRoom(room, closeChatDesc || ''); if (!result) { throw new Error(Logs.CLOSE_CHAT_REQUEST_FAILED_ERROR); } }; +export const sendCloseChatMessage = async (read: IRead, modify: IModify, app: IApp, rid: string) => { + const closeChatMessage: string = await getLivechatAgentConfig(read, rid, AppSetting.DialogflowCloseChatMessage); + if (closeChatMessage?.length > 0) { + await createMessage(rid, read, modify, { text: closeChatMessage }, app); + } +}; + export const performHandover = async ( app: IApp, modify: IModify, @@ -137,7 +142,7 @@ export const performHandover = async ( await removeBotTypingListener(modify, rid, DialogflowBotUsername); }; - const handleHandoverFailure = async (error?: string) => { + const handleHandoverFailure = async (error?: string, closeChatDesc?: string) => { const offlineMessage: string = await getLivechatAgentConfig(read, rid, AppSetting.DialogflowHandoverFailedMessage); const handoverFailure = { error: error || offlineMessage, @@ -153,7 +158,7 @@ export const performHandover = async ( await createMessage(rid, read, modify, { text: offlineMessage }, app); } await removeBotTypingIndicator(); - await closeChat(modify, read, rid); + await closeChat(modify, read, rid, app, closeChatDesc); }; // Fill livechatTransferData.targetDepartment param if required @@ -161,7 +166,7 @@ export const performHandover = async ( const targetDepartment: IDepartment = (await read.getLivechatReader().getLivechatDepartmentByIdOrName(targetDepartmentName)) as IDepartment; if (!targetDepartment) { modify.getAnalytics().sendEvent(getEventData(rid, EventName.ESCALATION_FAILED_DUE_TO_INVALID_TARGET_DEPARTMENT)); - await handleHandoverFailure(Logs.INVALID_DEPARTMENT_NAME); + await handleHandoverFailure(Logs.INVALID_DEPARTMENT_NAME, CloseChatDescription.INVALID_TARGET_DEPARTMENT); return false; } livechatTransferData.targetDepartment = targetDepartment.id; @@ -171,7 +176,7 @@ export const performHandover = async ( const serviceOnline = await read.getLivechatReader().isOnlineAsync(livechatTransferData.targetDepartment); if (!serviceOnline) { modify.getAnalytics().sendEvent(getEventData(rid, EventName.ESCALATION_FAILED_DUE_TO_LIVEAGENT_BOT_OFFLINE_OR_DISABLED)); - await handleHandoverFailure(); + await handleHandoverFailure('', CloseChatDescription.LIVEAGENT_BOT_OFFLINE_OR_DISABLED); return false; } @@ -186,6 +191,7 @@ export const performHandover = async ( await createMessage(rid, read, modify, { text: DefaultMessage.DEFAULT_DialogflowHandoverMessage }, app); } + let closeChatDesc; const result = await modify .getUpdater() .getLivechatUpdater() @@ -193,17 +199,20 @@ export const performHandover = async ( .catch((error) => { const { error: errorName, message: errorMessage } = error; let eventName = EventName.ESCALATION_FAILED_DUE_TO_NETWORK_OR_APP_ERROR; + closeChatDesc = CloseChatDescription.NETWORK_OR_APP_ERROR; if (errorName === 'error-forwarding-chat-same-department') { eventName = EventName.ESCALATION_FAILED_DUE_TO_SAME_TARGET_DEPARTMENT; + closeChatDesc = CloseChatDescription.SAME_TARGET_DEPARTMENT; } else if (errorName === 'error-user-is-offline') { eventName = EventName.ESCALATION_FAILED_DUE_TO_LIVEAGENT_BOT_OFFLINE_OR_DISABLED; + closeChatDesc = CloseChatDescription.LIVEAGENT_BOT_OFFLINE_OR_DISABLED; } modify.getAnalytics().sendEvent(getEventData(rid, eventName, { error: errorMessage })); throw new Error(`${Logs.HANDOVER_REQUEST_FAILED_ERROR} ${error}`); }); if (!result) { - await handleHandoverFailure(); + await handleHandoverFailure('', closeChatDesc); return false; } diff --git a/lib/SynchronousHandover.ts b/lib/SynchronousHandover.ts index 92dac17..f82efae 100644 --- a/lib/SynchronousHandover.ts +++ b/lib/SynchronousHandover.ts @@ -3,10 +3,10 @@ import { IApp } from '@rocket.chat/apps-engine/definition/IApp'; import { ILivechatRoom } from '@rocket.chat/apps-engine/definition/livechat'; import { IButtonElement } from '@rocket.chat/apps-engine/definition/uikit'; import { AppSetting } from '../config/Settings'; -import { IImageBlockExtended } from '../enum/Dialogflow'; +import { CloseChatDescription, IImageBlockExtended } from '../enum/Dialogflow'; import { Logs } from '../enum/Logs'; import { createMessage } from '../lib/Message'; -import { performHandover, updateRoomCustomFields } from './Room'; +import { closeChat, performHandover, updateRoomCustomFields } from './Room'; import { getLivechatAgentConfig } from './Settings'; export const incFallbackIntentAndSendResponse = async ( @@ -42,6 +42,7 @@ export const incFallbackIntentAndSendResponse = async ( console.error(Logs.EMPTY_HANDOVER_DEPARTMENT); const serviceUnavailable: string = await getLivechatAgentConfig(read, sessionId, AppSetting.DialogflowServiceUnavailableMessage); await createMessage(sessionId, read, modify, { text: serviceUnavailable }, app); + await closeChat(modify, read, sessionId, app, CloseChatDescription.INVALID_TARGET_DEPARTMENT); return; } diff --git a/lib/payloadAction.ts b/lib/payloadAction.ts index d6ddb69..2b94225 100644 --- a/lib/payloadAction.ts +++ b/lib/payloadAction.ts @@ -4,6 +4,7 @@ import { ILivechatRoom } from '@rocket.chat/apps-engine/definition/livechat'; import { AppSetting } from '../config/Settings'; import { ActionIds } from '../enum/ActionIds'; import { + CloseChatDescription, DialogflowRequestType, IDialogflowAction, IDialogflowCustomFields, @@ -77,7 +78,7 @@ export const handlePayloadActions = async ( } else if (actionName === ActionIds.CLOSE_CHAT) { const livechatRoom = (await read.getRoomReader().getById(rid)) as ILivechatRoom; if (livechatRoom && livechatRoom.isOpen) { - await closeChat(modify, read, rid); + await closeChat(modify, read, rid, app, CloseChatDescription.CLOSED_BY_AGENT); } } else if (actionName === ActionIds.NEW_WELCOME_EVENT) { const livechatRoom = (await read.getRoomReader().getById(rid)) as ILivechatRoom; @@ -115,6 +116,7 @@ export const handlePayloadActions = async ( console.error(error); const serviceUnavailable: string = await getLivechatAgentConfig(read, rid, AppSetting.DialogflowServiceUnavailableMessage); await createMessage(rid, read, modify, { text: serviceUnavailable }, app); + await closeChat(modify, read, rid, app, CloseChatDescription.NETWORK_OR_APP_ERROR); return; } } else if (actionName === ActionIds.CHANGE_LANGUAGE_CODE) { @@ -148,6 +150,7 @@ const sendChangeLanguageEvent = async (app: IApp, read: IRead, modify: IModify, console.error(`${Logs.DIALOGFLOW_REST_API_ERROR}: { roomID: ${rid} } ${getError(error)}`); const serviceUnavailable: string = await getLivechatAgentConfig(read, rid, AppSetting.DialogflowServiceUnavailableMessage); await createMessage(rid, read, modify, { text: serviceUnavailable }, app); + await closeChat(modify, read, rid, app, CloseChatDescription.NETWORK_OR_APP_ERROR); return; } }; @@ -210,6 +213,7 @@ export const sendWelcomeEventToDialogFlow = async ( console.error(`${Logs.DIALOGFLOW_REST_API_ERROR}: { roomID: ${rid} } ${getError(error)}`); const serviceUnavailable: string = await getLivechatAgentConfig(read, rid, AppSetting.DialogflowServiceUnavailableMessage); await createMessage(rid, read, modify, { text: serviceUnavailable }, app); + await closeChat(modify, read, rid, app, CloseChatDescription.NETWORK_OR_APP_ERROR); return; } }; diff --git a/lib/responseParameters.ts b/lib/responseParameters.ts index 40f4b80..3411b74 100644 --- a/lib/responseParameters.ts +++ b/lib/responseParameters.ts @@ -1,12 +1,13 @@ import { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; import { IApp } from '@rocket.chat/apps-engine/definition/IApp'; import { AppSetting } from '../config/Settings'; -import { DialogflowRequestType, IDialogflowMessage } from '../enum/Dialogflow'; +import { CloseChatDescription, DialogflowRequestType, IDialogflowMessage } from '../enum/Dialogflow'; import { Logs } from '../enum/Logs'; import { getError } from '../lib/Helper'; import { getRoomAssoc, retrieveDataByAssociation, updatePersistentData } from '../lib/Persistence'; import { Dialogflow } from './Dialogflow'; import { createDialogflowMessage, createMessage } from './Message'; +import { closeChat } from './Room'; import { getLivechatAgentConfig } from './Settings'; export const handleParameters = async ( @@ -42,9 +43,8 @@ const sendChangeLanguageEvent = async (app: IApp, read: IRead, modify: IModify, } catch (error) { console.error(`${Logs.DIALOGFLOW_REST_API_ERROR}: { roomID: ${rid} } ${getError(error)}`); const serviceUnavailable: string = await getLivechatAgentConfig(read, rid, AppSetting.DialogflowServiceUnavailableMessage); - await createMessage(rid, read, modify, { text: serviceUnavailable }, app); - + await closeChat(modify, read, rid, app, CloseChatDescription.NETWORK_OR_APP_ERROR); return; } }; diff --git a/lib/sendEventToDialogFlow.ts b/lib/sendEventToDialogFlow.ts index 82bab38..459ac4d 100644 --- a/lib/sendEventToDialogFlow.ts +++ b/lib/sendEventToDialogFlow.ts @@ -1,12 +1,13 @@ import { IHttp, IModify, IRead } from '@rocket.chat/apps-engine/definition/accessors'; import { IApp } from '@rocket.chat/apps-engine/definition/IApp'; import { AppSetting } from '../config/Settings'; -import { DialogflowRequestType, IDialogflowMessage, IMessageParameters, LanguageCode } from '../enum/Dialogflow'; +import { CloseChatDescription, DialogflowRequestType, IDialogflowMessage, IMessageParameters, LanguageCode } from '../enum/Dialogflow'; import { Logs } from '../enum/Logs'; import { getError } from '../lib/Helper'; import { Dialogflow } from './Dialogflow'; import { createDialogflowMessage, createMessage } from './Message'; import { getRoomAssoc, retrieveDataByAssociation } from './Persistence'; +import { closeChat } from './Room'; import { getLivechatAgentConfig } from './Settings'; export const sendEventToDialogFlow = async ( @@ -22,6 +23,7 @@ export const sendEventToDialogFlow = async ( console.error(Logs.INVALID_EVENT_DATA); const serviceUnavailable: string = await getLivechatAgentConfig(read, rid, AppSetting.DialogflowServiceUnavailableMessage); await createMessage(rid, read, modify, { text: serviceUnavailable }, app); + await closeChat(modify, read, rid, app, CloseChatDescription.INVALID_EVENT_DATA); return; } try { @@ -38,6 +40,7 @@ export const sendEventToDialogFlow = async ( console.error(`${Logs.DIALOGFLOW_REST_API_ERROR}: { roomID: ${rid} } ${getError(error)}`); const serviceUnavailable: string = await getLivechatAgentConfig(read, rid, AppSetting.DialogflowServiceUnavailableMessage); await createMessage(rid, read, modify, { text: serviceUnavailable }, app); + await closeChat(modify, read, rid, app, CloseChatDescription.NETWORK_OR_APP_ERROR); return; } }; diff --git a/package-lock.json b/package-lock.json index 6701bdd..7b4eb88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1022,7 +1022,7 @@ "lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, "lodash.merge": { @@ -1646,7 +1646,7 @@ "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", "dev": true }, "string-width": {