snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
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
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
This commit is contained in:
205
apps/meteor/server/lib/omnichannel/guests.ts
Normal file
205
apps/meteor/server/lib/omnichannel/guests.ts
Normal file
@@ -0,0 +1,205 @@
|
||||
import { Apps, AppEvents } from '@rocket.chat/apps';
|
||||
import type { ILivechatVisitor, IOmnichannelRoom, IUser, UserStatus } from '@rocket.chat/core-typings';
|
||||
import {
|
||||
LivechatVisitors,
|
||||
LivechatCustomField,
|
||||
LivechatInquiry,
|
||||
LivechatRooms,
|
||||
Messages,
|
||||
ReadReceipts,
|
||||
ReadReceiptsArchive,
|
||||
Subscriptions,
|
||||
LivechatContacts,
|
||||
Users,
|
||||
} from '@rocket.chat/models';
|
||||
import UAParser from 'ua-parser-js';
|
||||
|
||||
import { parseAgentCustomFields } from './Helper';
|
||||
import type { ICRMData } from './localTypes';
|
||||
import { livechatLogger } from './logger';
|
||||
import { trim } from '../../../lib/utils/stringUtils';
|
||||
import { hasPermissionAsync } from '../authorization/hasPermission';
|
||||
import { i18n } from '../i18n';
|
||||
import { FileUpload } from '../media/file-upload';
|
||||
import { notifyOnSubscriptionChanged, notifyOnLivechatInquiryChanged, notifyOnLivechatInquiryChangedByToken } from '../notifyListener';
|
||||
|
||||
export async function saveGuest(
|
||||
guestData: Pick<ILivechatVisitor, '_id' | 'name' | 'livechatData'> & { email?: string; phone?: string },
|
||||
userId: string,
|
||||
) {
|
||||
const { _id, name, email, phone, livechatData = {} } = guestData;
|
||||
|
||||
const visitor = await LivechatVisitors.findOneById(_id, { projection: { _id: 1 } });
|
||||
if (!visitor) {
|
||||
throw new Error('error-invalid-visitor');
|
||||
}
|
||||
|
||||
livechatLogger.debug({ msg: 'Saving guest', guestData });
|
||||
const updateData = {
|
||||
...(name && { name }),
|
||||
...(email && { email }),
|
||||
...(phone && { phone }),
|
||||
livechatData: {},
|
||||
};
|
||||
|
||||
const customFields: Record<string, any> = {};
|
||||
|
||||
if ((!userId || (await hasPermissionAsync(userId, 'edit-livechat-room-customfields'))) && Object.keys(livechatData).length) {
|
||||
livechatLogger.debug({ msg: 'Saving custom fields for visitor', visitorId: _id, livechatData });
|
||||
for await (const field of LivechatCustomField.findByScope('visitor')) {
|
||||
if (!livechatData.hasOwnProperty(field._id)) {
|
||||
continue;
|
||||
}
|
||||
const value = trim(livechatData[field._id]);
|
||||
if (value !== '' && field.regexp !== undefined && field.regexp !== '') {
|
||||
const regexp = new RegExp(field.regexp);
|
||||
if (!regexp.test(value)) {
|
||||
throw new Error(i18n.t('error-invalid-custom-field-value'));
|
||||
}
|
||||
}
|
||||
customFields[field._id] = value;
|
||||
}
|
||||
updateData.livechatData = customFields;
|
||||
livechatLogger.debug({
|
||||
msg: 'About to update custom fields for visitor',
|
||||
visitorId: _id,
|
||||
customFieldCount: Object.keys(customFields).length,
|
||||
});
|
||||
}
|
||||
const ret = await LivechatVisitors.saveGuestById(_id, updateData);
|
||||
|
||||
setImmediate(() => {
|
||||
void Apps.self?.triggerEvent(AppEvents.IPostLivechatGuestSaved, _id);
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
export async function removeGuest({ _id }: { _id: string }) {
|
||||
await cleanGuestHistory(_id);
|
||||
return LivechatVisitors.disableById(_id);
|
||||
}
|
||||
|
||||
export async function removeContactsByVisitorId({ _id }: { _id: string }) {
|
||||
// A visitor shouldn't have many contacts associated, so we can remove them like this
|
||||
const contacts = await LivechatContacts.findAllByVisitorId(_id).toArray();
|
||||
if (!contacts.length) {
|
||||
livechatLogger.debug({ msg: 'No contacts found for visitor', visitorId: _id });
|
||||
await removeGuest({ _id });
|
||||
}
|
||||
|
||||
// And a contact shouldn't have many channels associated, so we can do this
|
||||
livechatLogger.debug({ msg: 'Removing channels for contacts', visitorId: _id, contacts: contacts.map(({ _id }) => _id) });
|
||||
for await (const contact of contacts) {
|
||||
for await (const { visitor } of contact.channels) {
|
||||
await removeGuest({ _id: visitor.visitorId });
|
||||
}
|
||||
|
||||
await LivechatContacts.disableByVisitorId(_id);
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanGuestHistory(_id: string) {
|
||||
// This shouldn't be possible, but just in case
|
||||
if (!_id) {
|
||||
throw new Error('error-invalid-guest');
|
||||
}
|
||||
|
||||
// TODO: optimize function => instead of removing one by one, fetch the _ids of the rooms and then remove them in bulk
|
||||
const cursor = LivechatRooms.findByVisitorId(_id, { projection: { _id: 1 } });
|
||||
for await (const room of cursor) {
|
||||
await Promise.all([
|
||||
Subscriptions.removeByRoomId(room._id, {
|
||||
async onTrash(doc) {
|
||||
void notifyOnSubscriptionChanged(doc, 'removed');
|
||||
},
|
||||
}),
|
||||
FileUpload.removeFilesByRoomId(room._id),
|
||||
Messages.removeByRoomId(room._id),
|
||||
ReadReceipts.removeByRoomId(room._id),
|
||||
ReadReceiptsArchive.removeByRoomId(room._id),
|
||||
]);
|
||||
}
|
||||
|
||||
await LivechatRooms.removeByVisitorId(_id);
|
||||
|
||||
const livechatInquiries = await LivechatInquiry.findByVisitorIds([_id]).toArray();
|
||||
await LivechatInquiry.removeByIds(livechatInquiries.map(({ _id }) => _id));
|
||||
void notifyOnLivechatInquiryChanged(livechatInquiries, 'removed');
|
||||
}
|
||||
|
||||
export async function getLivechatRoomGuestInfo(room: IOmnichannelRoom) {
|
||||
const visitor = await LivechatVisitors.findOneEnabledById(room.v._id);
|
||||
if (!visitor) {
|
||||
throw new Error('error-invalid-visitor');
|
||||
}
|
||||
|
||||
const agent = room.servedBy?._id
|
||||
? await Users.findOneById<Pick<IUser, '_id' | 'customFields' | 'name' | 'username' | 'emails'>>(room.servedBy?._id, {
|
||||
projection: { _id: 1, customFields: 1, name: 1, username: 1, emails: 1 },
|
||||
})
|
||||
: null;
|
||||
|
||||
const ua = new UAParser();
|
||||
ua.setUA(visitor.userAgent || '');
|
||||
|
||||
const postData: ICRMData = {
|
||||
_id: room._id,
|
||||
label: room.fname || room.label, // using same field for compatibility
|
||||
topic: room.topic,
|
||||
createdAt: room.ts,
|
||||
lastMessageAt: room.lm,
|
||||
tags: room.tags,
|
||||
customFields: room.livechatData,
|
||||
visitor: {
|
||||
_id: visitor._id,
|
||||
token: visitor.token,
|
||||
name: visitor.name,
|
||||
username: visitor.username,
|
||||
department: visitor.department,
|
||||
ip: visitor.ip,
|
||||
os: ua.getOS().name && `${ua.getOS().name} ${ua.getOS().version}`,
|
||||
browser: ua.getBrowser().name && `${ua.getBrowser().name} ${ua.getBrowser().version}`,
|
||||
customFields: visitor.livechatData,
|
||||
},
|
||||
};
|
||||
|
||||
if (agent) {
|
||||
const customFields = parseAgentCustomFields(agent.customFields);
|
||||
|
||||
postData.agent = {
|
||||
_id: agent._id,
|
||||
username: agent.username,
|
||||
name: agent.name,
|
||||
...(customFields && { customFields }),
|
||||
};
|
||||
|
||||
if (agent.emails && agent.emails.length > 0) {
|
||||
postData.agent.email = agent.emails[0].address;
|
||||
}
|
||||
}
|
||||
|
||||
if (room.crmData) {
|
||||
postData.crmData = room.crmData;
|
||||
}
|
||||
|
||||
if (visitor.visitorEmails && visitor.visitorEmails.length > 0) {
|
||||
postData.visitor.email = visitor.visitorEmails;
|
||||
}
|
||||
if (visitor.phone && visitor.phone.length > 0) {
|
||||
postData.visitor.phone = visitor.phone;
|
||||
}
|
||||
|
||||
return postData;
|
||||
}
|
||||
|
||||
export async function notifyGuestStatusChanged(token: string, status: UserStatus) {
|
||||
const [, inquiryVisitorStatus] = await Promise.all([
|
||||
LivechatRooms.updateVisitorStatus(token, status),
|
||||
LivechatInquiry.updateVisitorStatus(token, status),
|
||||
]);
|
||||
|
||||
if (inquiryVisitorStatus.modifiedCount) {
|
||||
void notifyOnLivechatInquiryChangedByToken(token, 'updated', { v: { status } });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user