Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix] Send Close Chat Message to Client #172

Merged
merged 9 commits into from
Oct 6, 2022
2 changes: 1 addition & 1 deletion app.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@
"IPostLivechatRoomClosed",
"IUIKitLivechatInteractionHandler"
],
"commitHash": "69de13511e790a02257140867ae181511241c394"
"commitHash": "d036be146784ca79f84f0b2cae50f3cd518cf5a9"
}
4 changes: 2 additions & 2 deletions endpoints/IncomingEndpoint.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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;
Expand Down
13 changes: 13 additions & 0 deletions enum/Dialogflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
}
3 changes: 2 additions & 1 deletion handler/ExecuteLivechatBlockActionHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down
15 changes: 9 additions & 6 deletions handler/OnAgentUnassignedHandler.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -28,15 +29,17 @@ 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);
}
}

return;
}
}

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) {
Expand All @@ -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);
}
Expand Down
4 changes: 2 additions & 2 deletions handler/PostMessageSentHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
}

Expand Down
33 changes: 21 additions & 12 deletions lib/Room.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -153,15 +158,15 @@ 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
if (targetDepartmentName) {
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;
Expand All @@ -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;
}

Expand All @@ -186,24 +191,28 @@ export const performHandover = async (
await createMessage(rid, read, modify, { text: DefaultMessage.DEFAULT_DialogflowHandoverMessage }, app);
}

let closeChatDesc;
const result = await modify
.getUpdater()
.getLivechatUpdater()
.transferVisitor(visitor, livechatTransferData)
.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;
}

Expand Down
5 changes: 3 additions & 2 deletions lib/SynchronousHandover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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;
}

Expand Down
6 changes: 5 additions & 1 deletion lib/payloadAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
};
Expand Down Expand Up @@ -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;
}
};
6 changes: 3 additions & 3 deletions lib/responseParameters.ts
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down Expand Up @@ -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;
}
};
5 changes: 4 additions & 1 deletion lib/sendEventToDialogFlow.ts
Original file line number Diff line number Diff line change
@@ -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 (
Expand All @@ -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 {
Expand All @@ -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;
}
};
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.