Files
upstream-mirror 30810eecd7
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 2s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

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',
);