Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
264 lines
7.7 KiB
TypeScript
264 lines
7.7 KiB
TypeScript
import type { IOmnichannelRoom, IOmnichannelSystemMessage, IMessage } from '@rocket.chat/core-typings';
|
|
import { isEditedMessage, isOmnichannelRoom } from '@rocket.chat/core-typings';
|
|
import { LivechatRooms, Messages } from '@rocket.chat/models';
|
|
import type { Response } from '@rocket.chat/server-fetch';
|
|
|
|
import { callbacks } from '../../lib/callbacks';
|
|
import { getLivechatRoomGuestInfo } from '../../lib/omnichannel/guests';
|
|
import { sendRequest } from '../../lib/omnichannel/webhooks';
|
|
import { normalizeMessageFileUpload } from '../../lib/utils/functions/normalizeMessageFileUpload';
|
|
import { settings } from '../../settings';
|
|
|
|
type AdditionalFields =
|
|
| Record<string, unknown>
|
|
| {
|
|
departmentId: IOmnichannelRoom['departmentId'];
|
|
}
|
|
| {
|
|
departmentId: IOmnichannelRoom['departmentId'];
|
|
servedBy: IOmnichannelRoom['servedBy'];
|
|
}
|
|
| {
|
|
departmentId: IOmnichannelRoom['departmentId'];
|
|
servedBy: IOmnichannelRoom['servedBy'];
|
|
oldDepartmentId: IOmnichannelRoom['departmentId'];
|
|
oldServedBy: { _id: string; ts: Date; username?: string };
|
|
}
|
|
| {
|
|
departmentId: IOmnichannelRoom['departmentId'];
|
|
servedBy: IOmnichannelRoom['servedBy'];
|
|
closedAt: IOmnichannelRoom['closedAt'];
|
|
closedBy: IOmnichannelRoom['closedBy'];
|
|
closer: IOmnichannelRoom['closer'];
|
|
};
|
|
|
|
type OmnichannelRoomWithExtraFields = IOmnichannelRoom & {
|
|
oldServedBy?: { _id: string; ts: Date; username?: string };
|
|
oldDepartmentId?: IOmnichannelRoom['departmentId'];
|
|
};
|
|
|
|
type CRMActions =
|
|
| 'LivechatSessionStart'
|
|
| 'LivechatSessionQueued'
|
|
| 'LivechatSession'
|
|
| 'LivechatSessionTaken'
|
|
| 'LivechatSessionForwarded'
|
|
| 'LivechatEdit'
|
|
| 'Message'
|
|
| 'LeadCapture';
|
|
|
|
const msgNavType = 'livechat_navigation_history';
|
|
const msgClosingType = 'livechat-close';
|
|
|
|
export const isOmnichannelNavigationMessage = (message: IMessage): message is IOmnichannelSystemMessage => {
|
|
return message.t === msgNavType;
|
|
};
|
|
|
|
export const isOmnichannelClosingMessage = (message: IMessage): message is IOmnichannelSystemMessage => {
|
|
return message.t === msgClosingType;
|
|
};
|
|
|
|
export const sendMessageType = (msgType: string): boolean => {
|
|
switch (msgType) {
|
|
case msgClosingType:
|
|
return true;
|
|
case msgNavType:
|
|
return (
|
|
settings.get<boolean>('Livechat_Visitor_navigation_as_a_message') &&
|
|
settings.get<boolean>('Send_visitor_navigation_history_livechat_webhook_request')
|
|
);
|
|
default:
|
|
return false;
|
|
}
|
|
};
|
|
|
|
export const getAdditionalFieldsByType = (type: CRMActions, room: OmnichannelRoomWithExtraFields): AdditionalFields => {
|
|
const { departmentId, servedBy, closedAt, closedBy, closer, oldServedBy, oldDepartmentId } = room;
|
|
switch (type) {
|
|
case 'LivechatSessionStart':
|
|
case 'LivechatSessionQueued':
|
|
return { departmentId };
|
|
case 'LivechatSession':
|
|
return { departmentId, servedBy, closedAt, closedBy, closer };
|
|
case 'LivechatSessionTaken':
|
|
return { departmentId, servedBy };
|
|
case 'LivechatSessionForwarded':
|
|
return { departmentId, servedBy, oldDepartmentId, oldServedBy };
|
|
default:
|
|
return {};
|
|
}
|
|
};
|
|
|
|
export async function sendToCRM(
|
|
type: CRMActions,
|
|
room: OmnichannelRoomWithExtraFields,
|
|
includeMessages: boolean | IOmnichannelSystemMessage[] = true,
|
|
): Promise<OmnichannelRoomWithExtraFields> {
|
|
if (!settings.get('Livechat_webhookUrl')) {
|
|
return room;
|
|
}
|
|
|
|
const postData: Awaited<ReturnType<typeof getLivechatRoomGuestInfo>> & {
|
|
type: string;
|
|
messages: IOmnichannelSystemMessage[];
|
|
} = {
|
|
...(await getLivechatRoomGuestInfo(room)),
|
|
type,
|
|
messages: [],
|
|
};
|
|
|
|
let messages: IOmnichannelSystemMessage[] | null = null;
|
|
if (typeof includeMessages === 'boolean' && includeMessages) {
|
|
messages = await Messages.findVisibleByRoomId<IOmnichannelSystemMessage>(room._id, { sort: { ts: 1 } }).toArray();
|
|
} else if (includeMessages instanceof Array) {
|
|
messages = includeMessages;
|
|
}
|
|
|
|
if (messages) {
|
|
for (const message of messages) {
|
|
if (message.t && !sendMessageType(message.t)) {
|
|
continue;
|
|
}
|
|
const msg = {
|
|
_id: message._id,
|
|
username: message.u.username,
|
|
msg: message.msg || JSON.stringify(message.blocks),
|
|
...(message.blocks && message.blocks.length > 0 ? { blocks: message.blocks } : {}),
|
|
ts: message.ts,
|
|
rid: message.rid,
|
|
...(isEditedMessage(message) && { editedAt: message.editedAt }),
|
|
...(message.u.username !== postData.visitor.username && { agentId: message.u._id }),
|
|
...(isOmnichannelNavigationMessage(message) && { navigation: message.navigation }),
|
|
...(isOmnichannelClosingMessage(message) && { closingMessage: true }),
|
|
...(message.file && { file: message.file, attachments: message.attachments }),
|
|
};
|
|
|
|
const { u } = message;
|
|
postData.messages.push({ ...(await normalizeMessageFileUpload({ u, ...msg })), ...{ _updatedAt: message._updatedAt } });
|
|
}
|
|
}
|
|
|
|
const additionalData = getAdditionalFieldsByType(type, room);
|
|
const responseData = Object.assign(postData, additionalData);
|
|
|
|
// do not wait for the request to be answered
|
|
// this will avoid blocking the process of saving the message
|
|
void sendRequest(responseData, 5, async (response: Response) => {
|
|
if (!response) {
|
|
return;
|
|
}
|
|
const responseData = await response.text();
|
|
await LivechatRooms.saveCRMDataByRoomId(room._id, responseData);
|
|
});
|
|
|
|
return room;
|
|
}
|
|
|
|
callbacks.add(
|
|
'livechat.closeRoom',
|
|
async (params) => {
|
|
const { room } = params;
|
|
if (!settings.get('Livechat_webhook_on_close')) {
|
|
return params;
|
|
}
|
|
|
|
await sendToCRM('LivechatSession', room);
|
|
|
|
return params;
|
|
},
|
|
callbacks.priority.MEDIUM,
|
|
'livechat-send-crm-close-room',
|
|
);
|
|
|
|
callbacks.add(
|
|
'livechat.afterForwardChatToAgent',
|
|
async (params) => {
|
|
const { rid, oldServedBy } = params;
|
|
if (!settings.get('Livechat_webhook_on_forward')) {
|
|
return params;
|
|
}
|
|
|
|
const originalRoom = await LivechatRooms.findOneById(rid);
|
|
if (!originalRoom) {
|
|
return params;
|
|
}
|
|
|
|
const room = Object.assign(originalRoom, { oldServedBy });
|
|
await sendToCRM('LivechatSessionForwarded', room);
|
|
return params;
|
|
},
|
|
callbacks.priority.MEDIUM,
|
|
'livechat-send-crm-room-forwarded-to-agent',
|
|
);
|
|
|
|
callbacks.add(
|
|
'livechat.afterForwardChatToDepartment',
|
|
async (params) => {
|
|
const { rid, oldDepartmentId } = params;
|
|
if (!settings.get('Livechat_webhook_on_forward')) {
|
|
return params;
|
|
}
|
|
|
|
const originalRoom = await LivechatRooms.findOneById(rid);
|
|
if (!originalRoom) {
|
|
return params;
|
|
}
|
|
|
|
const room = Object.assign(originalRoom, { oldDepartmentId });
|
|
await sendToCRM('LivechatSessionForwarded', room);
|
|
return params;
|
|
},
|
|
callbacks.priority.MEDIUM,
|
|
'livechat-send-crm-room-forwarded-to-department',
|
|
);
|
|
|
|
callbacks.add(
|
|
'livechat.saveInfo',
|
|
async (room) => {
|
|
// Do not send to CRM if the chat is still open
|
|
if (!isOmnichannelRoom(room) || room.open) {
|
|
return room;
|
|
}
|
|
|
|
return sendToCRM('LivechatEdit', room);
|
|
},
|
|
callbacks.priority.MEDIUM,
|
|
'livechat-send-crm-save-info',
|
|
);
|
|
|
|
callbacks.add(
|
|
'afterOmnichannelSaveMessage',
|
|
async (message, { room }) => {
|
|
// if the message has a token, it was sent from the visitor
|
|
// if not, it was sent from the agent
|
|
if (message.token && !settings.get('Livechat_webhook_on_visitor_message')) {
|
|
return message;
|
|
}
|
|
if (!message.token && !settings.get('Livechat_webhook_on_agent_message')) {
|
|
return message;
|
|
}
|
|
// if the message has a type means it is a special message (like the closing comment), so skips
|
|
// unless the settings that handle with visitor navigation history are enabled
|
|
if (message.t && !sendMessageType(message.t)) {
|
|
return message;
|
|
}
|
|
|
|
await sendToCRM('Message', room, [message]);
|
|
return message;
|
|
},
|
|
callbacks.priority.MEDIUM,
|
|
'livechat-send-crm-message',
|
|
);
|
|
|
|
callbacks.add(
|
|
'livechat.leadCapture',
|
|
(room) => {
|
|
if (!settings.get('Livechat_webhook_on_capture')) {
|
|
return room;
|
|
}
|
|
return sendToCRM('LeadCapture', room, false);
|
|
},
|
|
callbacks.priority.MEDIUM,
|
|
'livechat-send-crm-lead-capture',
|
|
);
|