From 0640b4bb5110be19b0359ee3de13ff191114089a Mon Sep 17 00:00:00 2001 From: gustrb Date: Tue, 8 Oct 2024 10:29:32 -0300 Subject: [PATCH 01/37] feat: use room manager settings Now, we are using the new settings in order to be sure no agent gets assigned to the visitor that needs to be verified --- .../app/livechat/server/lib/RoutingManager.ts | 29 ++++++++++++++++++- packages/core-typings/src/IInquiry.ts | 4 ++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index 28e5c72efc16..d1363b8078f8 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -14,7 +14,7 @@ import type { import { LivechatInquiryStatus } from '@rocket.chat/core-typings'; import { License } from '@rocket.chat/license'; import { Logger } from '@rocket.chat/logger'; -import { LivechatInquiry, LivechatRooms, Subscriptions, Rooms, Users } from '@rocket.chat/models'; +import { LivechatInquiry, LivechatRooms, Subscriptions, Rooms, Users, LivechatContacts } from '@rocket.chat/models'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -226,6 +226,33 @@ export const RoutingManager: Routing = { }), ); + if (inquiry.v.contactId) { + const contact = await LivechatContacts.findOne({ + _id: inquiry.v.contactId, + }); + + if (!contact) { + logger.debug(`Could not find associated contact with visitor ${inquiry.v._id}`); + return room; + } + + if (contact.unknown && settings.get('Livechat_Block_Unknown_Contacts')) { + return room; + } + + const contactVerificationApp = settings.get('Livechat_Contact_Verification_App'); + if (contactVerificationApp !== '') { + if (!settings.get('Livechat_Request_Verification_On_First_Contact_Only')) { + return room; + } + + const verifiedChannels = contact.channels?.filter((channel) => channel.verified && channel.visitorId === inquiry.v._id) || []; + if (verifiedChannels.length === 0 && settings.get('Livechat_Block_Unverified_Contacts')) { + return room; + } + } + } + logger.debug(`Attempting to take Inquiry ${inquiry._id} [Agent ${agent.agentId}] `); const { _id, rid } = inquiry; diff --git a/packages/core-typings/src/IInquiry.ts b/packages/core-typings/src/IInquiry.ts index 5c9aa08e88d5..aed6ffd28701 100644 --- a/packages/core-typings/src/IInquiry.ts +++ b/packages/core-typings/src/IInquiry.ts @@ -37,7 +37,9 @@ export interface ILivechatInquiryRecord extends IRocketChatRecord { ts: Date; message: string; status: LivechatInquiryStatus; - v: Pick & { lastMessageTs?: Date }; + v: Pick & { + lastMessageTs?: Date; + }; t: 'l'; department?: string; From 335b5664501a945658c72c7bbf399ca47fb95e2a Mon Sep 17 00:00:00 2001 From: Gustavo Reis Bauer Date: Tue, 8 Oct 2024 10:34:40 -0300 Subject: [PATCH 02/37] Create clean-ravens-melt.md --- .changeset/clean-ravens-melt.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/clean-ravens-melt.md diff --git a/.changeset/clean-ravens-melt.md b/.changeset/clean-ravens-melt.md new file mode 100644 index 000000000000..12b7af105dab --- /dev/null +++ b/.changeset/clean-ravens-melt.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/core-typings": minor +--- + +Using the `Livechat_Block_Unknown_Contacts`, `Livechat_Contact_Verification_App`, `Livechat_Request_Verification_On_First_Contact_Only` settings to handle the visitor to a verificator app if needed. From 2cfa527ccc4dc5f801b2b511ad86d3bead8793e3 Mon Sep 17 00:00:00 2001 From: gustrb Date: Thu, 10 Oct 2024 10:16:23 -0300 Subject: [PATCH 03/37] chore: add contact id to inquiry --- apps/meteor/app/livechat/server/lib/Helper.ts | 5 +++-- apps/meteor/app/livechat/server/lib/QueueManager.ts | 1 + apps/meteor/app/livechat/server/lib/RoutingManager.ts | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index 263edeba7098..7bc1c7fa415d 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -168,7 +168,7 @@ export const createLivechatInquiry = async ({ }: { rid: string; name?: string; - guest?: Pick; + guest?: Pick; message?: string; initialStatus?: LivechatInquiryStatus; extraData?: Pick; @@ -188,7 +188,7 @@ export const createLivechatInquiry = async ({ const extraInquiryInfo = await callbacks.run('livechat.beforeInquiry', extraData); - const { _id, username, token, department, status = UserStatus.ONLINE, activity } = guest; + const { _id, username, token, department, status = UserStatus.ONLINE, activity, contactId } = guest; const ts = new Date(); @@ -211,6 +211,7 @@ export const createLivechatInquiry = async ({ token, status, ...(activity?.length && { activity }), + contactId, }, t: 'l', priorityWeight: LivechatPriorityWeight.NOT_SPECIFIED, diff --git a/apps/meteor/app/livechat/server/lib/QueueManager.ts b/apps/meteor/app/livechat/server/lib/QueueManager.ts index c6728d470870..33c42da27121 100644 --- a/apps/meteor/app/livechat/server/lib/QueueManager.ts +++ b/apps/meteor/app/livechat/server/lib/QueueManager.ts @@ -179,6 +179,7 @@ export class QueueManager { department: Match.Maybe(String), name: Match.Maybe(String), activity: Match.Maybe([String]), + contactId: Match.Maybe(String), }), ); diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index d1363b8078f8..d7a92ba613e1 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -21,6 +21,7 @@ import { Meteor } from 'meteor/meteor'; import { callbacks } from '../../../../lib/callbacks'; import { notifyOnLivechatInquiryChangedById, notifyOnLivechatInquiryChanged } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; +import { isSingleContactEnabled } from './Contacts'; import { createLivechatSubscription, dispatchAgentDelegated, @@ -226,7 +227,7 @@ export const RoutingManager: Routing = { }), ); - if (inquiry.v.contactId) { + if (isSingleContactEnabled() && inquiry.v.contactId) { const contact = await LivechatContacts.findOne({ _id: inquiry.v.contactId, }); From d580ca4095b432670a2a3ffee567eaba2553d3bf Mon Sep 17 00:00:00 2001 From: gustrb Date: Thu, 10 Oct 2024 10:55:07 -0300 Subject: [PATCH 04/37] chore: add contact unverification --- apps/meteor/app/livechat/server/lib/RoutingManager.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index d7a92ba613e1..56ae936aa681 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -21,7 +21,7 @@ import { Meteor } from 'meteor/meteor'; import { callbacks } from '../../../../lib/callbacks'; import { notifyOnLivechatInquiryChangedById, notifyOnLivechatInquiryChanged } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; -import { isSingleContactEnabled } from './Contacts'; +import { isSingleContactEnabled, unverifyContactChannel } from './Contacts'; import { createLivechatSubscription, dispatchAgentDelegated, @@ -238,17 +238,24 @@ export const RoutingManager: Routing = { } if (contact.unknown && settings.get('Livechat_Block_Unknown_Contacts')) { + // TODO: we currently don't have the ability to block contacts, when it gets added we must add a call to it here :P return room; } const contactVerificationApp = settings.get('Livechat_Contact_Verification_App'); + // Note: Non-empty `Livechat_Contact_Verification_App` means the user has a Contact Verification App setup, + // therefore, we must give the app control over the room if (contactVerificationApp !== '') { + // Note: If it is not `Livechat_Request_Verification_On_First_Contact_Only` it means that even though the contact + // was already verified, we must verify it again in order to handle the livechat conversation down to the queue if (!settings.get('Livechat_Request_Verification_On_First_Contact_Only')) { + await unverifyContactChannel(contact, room.source.type, inquiry.v._id); return room; } - const verifiedChannels = contact.channels?.filter((channel) => channel.verified && channel.visitorId === inquiry.v._id) || []; + const verifiedChannels = contact.channels?.filter((channel) => channel.verified && channel.name === room.source.type) || []; if (verifiedChannels.length === 0 && settings.get('Livechat_Block_Unverified_Contacts')) { + await unverifyContactChannel(contact, room.source.type, inquiry.v._id); return room; } } From d7b302af7f1df40c121b9767c49fb434115a6b39 Mon Sep 17 00:00:00 2001 From: gustrb Date: Thu, 10 Oct 2024 20:15:07 -0300 Subject: [PATCH 05/37] chore: pick settings commit --- .../livechat-enterprise/server/settings.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/apps/meteor/ee/app/livechat-enterprise/server/settings.ts b/apps/meteor/ee/app/livechat-enterprise/server/settings.ts index 38023db73b11..304d46381837 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/settings.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/settings.ts @@ -165,6 +165,48 @@ export const createSettings = async (): Promise => { modules: ['livechat-enterprise'], invalidValue: '', }); + + await this.add('Livechat_Block_Unknown_Contacts', false, { + type: 'boolean', + public: true, + hidden: true, + enableQuery: omnichannelEnabledQuery, + invalidValue: false, + section: 'Contact_identification', + enterprise: true, + modules: ['livechat-enterprise'], + }); + await this.add('Livechat_Block_Unverified_Contacts', false, { + type: 'boolean', + public: true, + hidden: true, + enableQuery: omnichannelEnabledQuery, + invalidValue: false, + section: 'Contact_identification', + enterprise: true, + modules: ['livechat-enterprise'], + }); + await this.add('Livechat_Contact_Verification_App', '', { + type: 'select', + public: true, + hidden: true, + values: [{ key: 'VerifyChat', i18nLabel: 'VerifyChat' }], + enableQuery: omnichannelEnabledQuery, + invalidValue: '', + section: 'Contact_identification', + enterprise: true, + modules: ['livechat-enterprise'], + }); + await this.add('Livechat_Request_Verification_On_First_Contact_Only', false, { + type: 'boolean', + public: true, + hidden: true, + enableQuery: omnichannelEnabledQuery, + invalidValue: false, + section: 'Contact_identification', + enterprise: true, + modules: ['livechat-enterprise'], + }); }); await this.add('Livechat_Block_Unknown_Contacts', false, { From 51da9f71fa651f5465ca43c759a1e0ca5788a1f7 Mon Sep 17 00:00:00 2001 From: gustrb Date: Fri, 11 Oct 2024 15:20:05 -0300 Subject: [PATCH 06/37] chore: merge ifs --- apps/meteor/app/livechat/server/lib/RoutingManager.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index 56ae936aa681..7d3377ba8fad 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -238,7 +238,6 @@ export const RoutingManager: Routing = { } if (contact.unknown && settings.get('Livechat_Block_Unknown_Contacts')) { - // TODO: we currently don't have the ability to block contacts, when it gets added we must add a call to it here :P return room; } @@ -248,13 +247,11 @@ export const RoutingManager: Routing = { if (contactVerificationApp !== '') { // Note: If it is not `Livechat_Request_Verification_On_First_Contact_Only` it means that even though the contact // was already verified, we must verify it again in order to handle the livechat conversation down to the queue - if (!settings.get('Livechat_Request_Verification_On_First_Contact_Only')) { - await unverifyContactChannel(contact, room.source.type, inquiry.v._id); - return room; - } - const verifiedChannels = contact.channels?.filter((channel) => channel.verified && channel.name === room.source.type) || []; - if (verifiedChannels.length === 0 && settings.get('Livechat_Block_Unverified_Contacts')) { + if ( + !settings.get('Livechat_Request_Verification_On_First_Contact_Only') || + (verifiedChannels.length === 0 && settings.get('Livechat_Block_Unverified_Contacts')) + ) { await unverifyContactChannel(contact, room.source.type, inquiry.v._id); return room; } From 53a4c39ec24f52d71997a1a67d06f4303ffc2f9f Mon Sep 17 00:00:00 2001 From: gustrb Date: Sun, 13 Oct 2024 11:18:28 -0300 Subject: [PATCH 07/37] chore: improve the code --- .changeset/clean-ravens-melt.md | 5 ++- .../app/livechat/server/lib/RoutingManager.ts | 36 +++++++++++-------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/.changeset/clean-ravens-melt.md b/.changeset/clean-ravens-melt.md index 12b7af105dab..e6bef4d1d10f 100644 --- a/.changeset/clean-ravens-melt.md +++ b/.changeset/clean-ravens-melt.md @@ -3,4 +3,7 @@ "@rocket.chat/core-typings": minor --- -Using the `Livechat_Block_Unknown_Contacts`, `Livechat_Contact_Verification_App`, `Livechat_Request_Verification_On_First_Contact_Only` settings to handle the visitor to a verificator app if needed. +Added three new settings to control how the agent might be handled down to the Omnichannel queue, they are: +- Livechat_Block_Unknown_Contacts: if the contact associated with the livechat visitor is unknown, the conversation won't be handled down to an agent; +- Livechat_Contact_Verification_App: Defines the app that is going to be used to verify the contact; +- Livechat_Request_Verification_On_First_Contact_Only: if the contact associated with the livechat visitor should be verified every time he starts a new conversation or only once. diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index 7d3377ba8fad..317a0811f62e 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -1,4 +1,5 @@ import { Apps, AppEvents } from '@rocket.chat/apps'; +import type { ILivechatContact } from '@rocket.chat/apps-engine/definition/livechat'; import { Message, Omnichannel } from '@rocket.chat/core-services'; import type { ILivechatInquiryRecord, @@ -227,9 +228,26 @@ export const RoutingManager: Routing = { }), ); + logger.debug(`Attempting to take Inquiry ${inquiry._id} [Agent ${agent.agentId}] `); + + const { _id, rid } = inquiry; + if (!room?.open) { + logger.debug(`Cannot take Inquiry ${inquiry._id}: Room is closed`); + return room; + } + + if (room.servedBy && room.servedBy._id === agent.agentId) { + logger.debug(`Cannot take Inquiry ${inquiry._id}: Already taken by agent ${room.servedBy._id}`); + return room; + } + if (isSingleContactEnabled() && inquiry.v.contactId) { - const contact = await LivechatContacts.findOne({ - _id: inquiry.v.contactId, + const contact = await LivechatContacts.findOneById>(inquiry.v.contactId, { + projection: { + _id: 1, + unknown: 1, + channels: 1, + }, }); if (!contact) { @@ -238,6 +256,7 @@ export const RoutingManager: Routing = { } if (contact.unknown && settings.get('Livechat_Block_Unknown_Contacts')) { + logger.debug(`Contact ${inquiry.v._id} is unknown and Livechat_Block_Unknown_Contacts so we can't handle it over to the queue`); return room; } @@ -258,19 +277,6 @@ export const RoutingManager: Routing = { } } - logger.debug(`Attempting to take Inquiry ${inquiry._id} [Agent ${agent.agentId}] `); - - const { _id, rid } = inquiry; - if (!room?.open) { - logger.debug(`Cannot take Inquiry ${inquiry._id}: Room is closed`); - return room; - } - - if (room.servedBy && room.servedBy._id === agent.agentId) { - logger.debug(`Cannot take Inquiry ${inquiry._id}: Already taken by agent ${room.servedBy._id}`); - return room; - } - try { await callbacks.run('livechat.checkAgentBeforeTakeInquiry', { agent, From 1c09a1799f95b0ee94a7f853896242796b37eb41 Mon Sep 17 00:00:00 2001 From: gustrb Date: Sun, 13 Oct 2024 11:23:47 -0300 Subject: [PATCH 08/37] feat: added `Livechat_Block_Unverified_Contacts` setting support --- .changeset/clean-ravens-melt.md | 5 +++-- .../app/livechat/server/lib/RoutingManager.ts | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.changeset/clean-ravens-melt.md b/.changeset/clean-ravens-melt.md index e6bef4d1d10f..799e48a7e442 100644 --- a/.changeset/clean-ravens-melt.md +++ b/.changeset/clean-ravens-melt.md @@ -5,5 +5,6 @@ Added three new settings to control how the agent might be handled down to the Omnichannel queue, they are: - Livechat_Block_Unknown_Contacts: if the contact associated with the livechat visitor is unknown, the conversation won't be handled down to an agent; -- Livechat_Contact_Verification_App: Defines the app that is going to be used to verify the contact; -- Livechat_Request_Verification_On_First_Contact_Only: if the contact associated with the livechat visitor should be verified every time he starts a new conversation or only once. +- Livechat_Block_Unverified_Contacts: if the contact has no verified channel, the conversation won't be handled down to an agent; +- Livechat_Request_Verification_On_First_Contact_Only: if the contact associated with the livechat visitor should be verified every time he starts a new conversation or only once; +- Livechat_Contact_Verification_App: Defines the app that is going to be used to verify the contact. \ No newline at end of file diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index 317a0811f62e..45c5c18ef2bc 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -260,16 +260,27 @@ export const RoutingManager: Routing = { return room; } + // Note: this should either be 1 or 0, if he is verified or not, but since we have an array of channels instead of a set there is no strong + // guarantee that is the case :P + const isContactVerified = + (contact.channels?.filter((channel) => channel.verified && channel.name === room.source.type) || []).length > 0; + + if (!isContactVerified && settings.get('Livechat_Block_Unverified_Contacts')) { + logger.debug( + `Contact ${inquiry.v._id} is not verified and Livechat_Block_Unverified_Contacts is enabled so we can't handle him down to the queue`, + ); + return room; + } + const contactVerificationApp = settings.get('Livechat_Contact_Verification_App'); // Note: Non-empty `Livechat_Contact_Verification_App` means the user has a Contact Verification App setup, // therefore, we must give the app control over the room if (contactVerificationApp !== '') { // Note: If it is not `Livechat_Request_Verification_On_First_Contact_Only` it means that even though the contact // was already verified, we must verify it again in order to handle the livechat conversation down to the queue - const verifiedChannels = contact.channels?.filter((channel) => channel.verified && channel.name === room.source.type) || []; if ( !settings.get('Livechat_Request_Verification_On_First_Contact_Only') || - (verifiedChannels.length === 0 && settings.get('Livechat_Block_Unverified_Contacts')) + (!isContactVerified && settings.get('Livechat_Block_Unverified_Contacts')) ) { await unverifyContactChannel(contact, room.source.type, inquiry.v._id); return room; From f095ab6d6e350653b622915e416280f67bb986be Mon Sep 17 00:00:00 2001 From: gustrb Date: Sun, 13 Oct 2024 11:25:28 -0300 Subject: [PATCH 09/37] fix: oopsie typo --- .changeset/clean-ravens-melt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/clean-ravens-melt.md b/.changeset/clean-ravens-melt.md index 799e48a7e442..4caa49236fe8 100644 --- a/.changeset/clean-ravens-melt.md +++ b/.changeset/clean-ravens-melt.md @@ -3,7 +3,7 @@ "@rocket.chat/core-typings": minor --- -Added three new settings to control how the agent might be handled down to the Omnichannel queue, they are: +Added four new settings to control how the agent might be handled down to the Omnichannel queue, they are: - Livechat_Block_Unknown_Contacts: if the contact associated with the livechat visitor is unknown, the conversation won't be handled down to an agent; - Livechat_Block_Unverified_Contacts: if the contact has no verified channel, the conversation won't be handled down to an agent; - Livechat_Request_Verification_On_First_Contact_Only: if the contact associated with the livechat visitor should be verified every time he starts a new conversation or only once; From 657c8a8c13c8a0677b9ebcec436b887f88215aea Mon Sep 17 00:00:00 2001 From: gustrb Date: Mon, 14 Oct 2024 10:06:33 -0300 Subject: [PATCH 10/37] chore: sorry linter --- .../app/livechat/server/lib/LivechatTyped.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index b43edaae1f15..d4863d8ef1b8 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -446,6 +446,55 @@ class LivechatClass { } } + if (isSingleContactEnabled()) { + let { contactId } = visitor; + + if (!contactId) { + const visitorContact = await LivechatVisitors.findOne< + Pick + >(visitor._id, { + projection: { + name: 1, + contactManager: 1, + livechatData: 1, + phone: 1, + visitorEmails: 1, + username: 1, + contactId: 1, + }, + }); + + contactId = visitorContact?.contactId; + } + + if (!contactId) { + // ensure that old visitors have a contact + contactId = await createContactFromVisitor(visitor); + } + + const contact = await LivechatContacts.findOneById>(contactId, { + projection: { _id: 1, channels: 1 }, + }); + + if (contact) { + const channel = contact.channels?.find( + (channel: ILivechatContactChannel) => channel.name === roomInfo.source?.type && channel.visitorId === visitor._id, + ); + + if (!channel) { + Livechat.logger.debug(`Adding channel for contact ${contact._id}`); + + await LivechatContacts.addChannel(contact._id, { + name: roomInfo.source?.label || roomInfo.source?.type.toString() || OmnichannelSourceType.OTHER, + visitorId: visitor._id, + blocked: false, + verified: false, + details: roomInfo.source, + }); + } + } + } + // delegate room creation to QueueManager Livechat.logger.debug(`Calling QueueManager to request a room for visitor ${visitor._id}`); From 43b8c0be1a21223e413c4ab0f2bdf60bb4ba2dab Mon Sep 17 00:00:00 2001 From: gustrb Date: Mon, 14 Oct 2024 11:10:49 -0300 Subject: [PATCH 11/37] chore: move import --- apps/meteor/app/livechat/server/lib/RoutingManager.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index 45c5c18ef2bc..b0cc1488e9bd 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -1,5 +1,4 @@ import { Apps, AppEvents } from '@rocket.chat/apps'; -import type { ILivechatContact } from '@rocket.chat/apps-engine/definition/livechat'; import { Message, Omnichannel } from '@rocket.chat/core-services'; import type { ILivechatInquiryRecord, @@ -12,7 +11,7 @@ import type { InquiryWithAgentInfo, TransferData, } from '@rocket.chat/core-typings'; -import { LivechatInquiryStatus } from '@rocket.chat/core-typings'; +import { LivechatInquiryStatus, ILivechatContact } from '@rocket.chat/core-typings'; import { License } from '@rocket.chat/license'; import { Logger } from '@rocket.chat/logger'; import { LivechatInquiry, LivechatRooms, Subscriptions, Rooms, Users, LivechatContacts } from '@rocket.chat/models'; From 588c69c9491516cd66e906facb81518405029c6d Mon Sep 17 00:00:00 2001 From: gustrb Date: Mon, 14 Oct 2024 13:00:55 -0300 Subject: [PATCH 12/37] fix: sorry linter --- apps/meteor/app/livechat/server/lib/RoutingManager.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index b0cc1488e9bd..f11359c1c992 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -10,8 +10,9 @@ import type { SelectedAgent, InquiryWithAgentInfo, TransferData, + ILivechatContact, } from '@rocket.chat/core-typings'; -import { LivechatInquiryStatus, ILivechatContact } from '@rocket.chat/core-typings'; +import { LivechatInquiryStatus } from '@rocket.chat/core-typings'; import { License } from '@rocket.chat/license'; import { Logger } from '@rocket.chat/logger'; import { LivechatInquiry, LivechatRooms, Subscriptions, Rooms, Users, LivechatContacts } from '@rocket.chat/models'; From 664cb21e28778263795701e750c73975ea8a943f Mon Sep 17 00:00:00 2001 From: gustrb Date: Mon, 14 Oct 2024 17:15:20 -0300 Subject: [PATCH 13/37] chore: not pick room if it is with an unverified contact --- .../meteor/app/livechat/server/api/v1/room.ts | 9 ++++ .../app/livechat/server/api/v1/videoCall.ts | 8 +++ apps/meteor/app/livechat/server/lib/Helper.ts | 1 + .../app/livechat/server/lib/LivechatTyped.ts | 4 ++ .../app/livechat/server/lib/QueueManager.ts | 11 ++++ .../app/livechat/server/lib/RoutingManager.ts | 51 +------------------ .../server/methods/returnAsInquiry.ts | 4 ++ .../livechat/server/methods/sendTranscript.ts | 4 ++ .../livechat/server/methods/takeInquiry.ts | 4 ++ .../app/livechat/server/methods/transfer.ts | 4 ++ .../server/services/omnichannel/service.ts | 40 ++++++++++++++- .../src/types/IOmnichannelService.ts | 1 + 12 files changed, 90 insertions(+), 51 deletions(-) diff --git a/apps/meteor/app/livechat/server/api/v1/room.ts b/apps/meteor/app/livechat/server/api/v1/room.ts index 7d52617e074a..a54db3496070 100644 --- a/apps/meteor/app/livechat/server/api/v1/room.ts +++ b/apps/meteor/app/livechat/server/api/v1/room.ts @@ -301,6 +301,11 @@ API.v1.addRoute( throw new Error('error-mac-limit-reached'); } + if (await Omnichannel.isUnverifiedContact(room)) { + // TODO: register the error + throw new Error('error-unverified-contact'); + } + const guest = await LivechatVisitors.findOneEnabledById(room.v?._id); if (!guest) { throw new Error('error-invalid-visitor'); @@ -355,6 +360,10 @@ API.v1.addRoute( throw new Error('error-mac-limit-reached'); } + if (await Omnichannel.isUnverifiedContact(room)) { + throw new Error('error-unverified-contact'); + } + if (!(await canAccessRoomAsync(room, user))) { throw new Error('error-not-allowed'); } diff --git a/apps/meteor/app/livechat/server/api/v1/videoCall.ts b/apps/meteor/app/livechat/server/api/v1/videoCall.ts index dd9c701e6495..3361d881c061 100644 --- a/apps/meteor/app/livechat/server/api/v1/videoCall.ts +++ b/apps/meteor/app/livechat/server/api/v1/videoCall.ts @@ -33,6 +33,10 @@ API.v1.addRoute( throw new Error('error-mac-limit-reached'); } + if (await Omnichannel.isUnverifiedContact(room as IOmnichannelRoom)) { + throw new Error('error-unverified-contact'); + } + const webrtcCallingAllowed = rcSettings.get('WebRTC_Enabled') === true && rcSettings.get('Omnichannel_call_provider') === 'WebRTC'; if (!webrtcCallingAllowed) { throw new Error('webRTC calling not enabled'); @@ -95,6 +99,10 @@ API.v1.addRoute( throw new Error('error-mac-limit-reached'); } + if (await Omnichannel.isUnverifiedContact(room as IOmnichannelRoom)) { + throw new Error('error-unverified-contact'); + } + const call = await Messages.findOneById(callId); if (!call || call.t !== 'livechat_webrtc_video_call') { throw new Error('invalid-callId'); diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index 7bc1c7fa415d..878a65daac50 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -618,6 +618,7 @@ export const forwardRoomToDepartment = async (room: IOmnichannelRoom, guest: ILi if ( !RoutingManager.getConfig()?.autoAssignAgent || !(await Omnichannel.isWithinMACLimit(room)) || + (await Omnichannel.isUnverifiedContact(room)) || (department?.allowReceiveForwardOffline && !(await LivechatTyped.checkOnlineAgents(departmentId))) ) { logger.debug(`Room ${room._id} will be on department queue`); diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index d4863d8ef1b8..32924773f17e 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -1478,6 +1478,10 @@ class LivechatClass { throw new Error('error-mac-limit-reached'); } + if (await Omnichannel.isUnverifiedContact(room as IOmnichannelRoom)) { + throw new Error('error-unverified-contact'); + } + const { _id, username, name, utcOffset } = user; const transcriptRequest = { requestedAt: new Date(), diff --git a/apps/meteor/app/livechat/server/lib/QueueManager.ts b/apps/meteor/app/livechat/server/lib/QueueManager.ts index 33c42da27121..f2a01aa836be 100644 --- a/apps/meteor/app/livechat/server/lib/QueueManager.ts +++ b/apps/meteor/app/livechat/server/lib/QueueManager.ts @@ -25,6 +25,7 @@ import { } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; import { i18n } from '../../../utils/lib/i18n'; +import { unverifyContactChannel } from './Contacts'; import { createLivechatRoom, createLivechatInquiry, allowAgentSkipQueue } from './Helper'; import { Livechat } from './LivechatTyped'; import { RoutingManager } from './RoutingManager'; @@ -92,6 +93,12 @@ export class QueueManager { return; } + if (inquiry.v.contactId && (await Omnichannel.isUnverifiedContact(room))) { + logger.error({ msg: 'Contact is not verified, not routing inquiry', inquiry }); + await Promise.all([unverifyContactChannel(inquiry.v.contactId, room.source.type, inquiry.v._id), saveQueueInquiry(inquiry)]); + return; + } + const inquiryAgent = await RoutingManager.delegateAgent(defaultAgent, inquiry); logger.debug(`Delegating inquiry with id ${inquiry._id} to agent ${defaultAgent?.username}`); await callbacks.run('livechat.beforeRouteChat', inquiry, inquiryAgent); @@ -122,6 +129,10 @@ export class QueueManager { return LivechatInquiryStatus.QUEUED; } + if (await Omnichannel.isUnverifiedContact(room)) { + return LivechatInquiryStatus.QUEUED; + } + if (RoutingManager.getConfig()?.autoAssignAgent) { return LivechatInquiryStatus.READY; } diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index f11359c1c992..28e5c72efc16 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -10,19 +10,17 @@ import type { SelectedAgent, InquiryWithAgentInfo, TransferData, - ILivechatContact, } from '@rocket.chat/core-typings'; import { LivechatInquiryStatus } from '@rocket.chat/core-typings'; import { License } from '@rocket.chat/license'; import { Logger } from '@rocket.chat/logger'; -import { LivechatInquiry, LivechatRooms, Subscriptions, Rooms, Users, LivechatContacts } from '@rocket.chat/models'; +import { LivechatInquiry, LivechatRooms, Subscriptions, Rooms, Users } from '@rocket.chat/models'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import { callbacks } from '../../../../lib/callbacks'; import { notifyOnLivechatInquiryChangedById, notifyOnLivechatInquiryChanged } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; -import { isSingleContactEnabled, unverifyContactChannel } from './Contacts'; import { createLivechatSubscription, dispatchAgentDelegated, @@ -241,53 +239,6 @@ export const RoutingManager: Routing = { return room; } - if (isSingleContactEnabled() && inquiry.v.contactId) { - const contact = await LivechatContacts.findOneById>(inquiry.v.contactId, { - projection: { - _id: 1, - unknown: 1, - channels: 1, - }, - }); - - if (!contact) { - logger.debug(`Could not find associated contact with visitor ${inquiry.v._id}`); - return room; - } - - if (contact.unknown && settings.get('Livechat_Block_Unknown_Contacts')) { - logger.debug(`Contact ${inquiry.v._id} is unknown and Livechat_Block_Unknown_Contacts so we can't handle it over to the queue`); - return room; - } - - // Note: this should either be 1 or 0, if he is verified or not, but since we have an array of channels instead of a set there is no strong - // guarantee that is the case :P - const isContactVerified = - (contact.channels?.filter((channel) => channel.verified && channel.name === room.source.type) || []).length > 0; - - if (!isContactVerified && settings.get('Livechat_Block_Unverified_Contacts')) { - logger.debug( - `Contact ${inquiry.v._id} is not verified and Livechat_Block_Unverified_Contacts is enabled so we can't handle him down to the queue`, - ); - return room; - } - - const contactVerificationApp = settings.get('Livechat_Contact_Verification_App'); - // Note: Non-empty `Livechat_Contact_Verification_App` means the user has a Contact Verification App setup, - // therefore, we must give the app control over the room - if (contactVerificationApp !== '') { - // Note: If it is not `Livechat_Request_Verification_On_First_Contact_Only` it means that even though the contact - // was already verified, we must verify it again in order to handle the livechat conversation down to the queue - if ( - !settings.get('Livechat_Request_Verification_On_First_Contact_Only') || - (!isContactVerified && settings.get('Livechat_Block_Unverified_Contacts')) - ) { - await unverifyContactChannel(contact, room.source.type, inquiry.v._id); - return room; - } - } - } - try { await callbacks.run('livechat.checkAgentBeforeTakeInquiry', { agent, diff --git a/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts b/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts index bf76519a5afb..6e8c43c438eb 100644 --- a/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts +++ b/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts @@ -34,6 +34,10 @@ Meteor.methods({ throw new Meteor.Error('error-mac-limit-reached', 'MAC limit reached', { method: 'livechat:returnAsInquiry' }); } + if (await Omnichannel.isUnverifiedContact(room)) { + throw new Meteor.Error('error-unverified-contact', 'Unverified contact', { method: 'livechat:returnAsInquiry' }); + } + if (!room.open) { throw new Meteor.Error('room-closed', 'Room closed', { method: 'livechat:returnAsInquiry' }); } diff --git a/apps/meteor/app/livechat/server/methods/sendTranscript.ts b/apps/meteor/app/livechat/server/methods/sendTranscript.ts index 00287fa89327..e06707d38d60 100644 --- a/apps/meteor/app/livechat/server/methods/sendTranscript.ts +++ b/apps/meteor/app/livechat/server/methods/sendTranscript.ts @@ -39,6 +39,10 @@ Meteor.methods({ throw new Meteor.Error('error-mac-limit-reached', 'MAC limit reached', { method: 'livechat:sendTranscript' }); } + if (await Omnichannel.isUnverifiedContact(room)) { + throw new Meteor.Error('error-unverified-contact', 'Unverified contact', { method: 'livechat:sendTranscript' }); + } + return sendTranscript({ token, rid, email, subject, user }); }, }); diff --git a/apps/meteor/app/livechat/server/methods/takeInquiry.ts b/apps/meteor/app/livechat/server/methods/takeInquiry.ts index 733cbd995208..d04f387a2460 100644 --- a/apps/meteor/app/livechat/server/methods/takeInquiry.ts +++ b/apps/meteor/app/livechat/server/methods/takeInquiry.ts @@ -54,6 +54,10 @@ export const takeInquiry = async ( throw new Error('error-mac-limit-reached'); } + if (await Omnichannel.isUnverifiedContact(room)) { + throw new Error('error-unverified-contact'); + } + const agent = { agentId: user._id, username: user.username, diff --git a/apps/meteor/app/livechat/server/methods/transfer.ts b/apps/meteor/app/livechat/server/methods/transfer.ts index e0516fa27981..0dbde5690aec 100644 --- a/apps/meteor/app/livechat/server/methods/transfer.ts +++ b/apps/meteor/app/livechat/server/methods/transfer.ts @@ -54,6 +54,10 @@ Meteor.methods({ throw new Meteor.Error('error-mac-limit-reached', 'MAC limit reached', { method: 'livechat:transfer' }); } + if (await Omnichannel.isUnverifiedContact(room)) { + throw new Meteor.Error('error-unverified-contact', 'Unverified contact', { method: 'livechat:transfer' }); + } + const subscription = await Subscriptions.findOneByRoomIdAndUserId(room._id, uid, { projection: { _id: 1 }, }); diff --git a/apps/meteor/server/services/omnichannel/service.ts b/apps/meteor/server/services/omnichannel/service.ts index 34599cbc7a33..32d16118c935 100644 --- a/apps/meteor/server/services/omnichannel/service.ts +++ b/apps/meteor/server/services/omnichannel/service.ts @@ -1,9 +1,11 @@ import { ServiceClassInternal } from '@rocket.chat/core-services'; import type { IOmnichannelService } from '@rocket.chat/core-services'; -import type { AtLeast, IOmnichannelQueue, IOmnichannelRoom } from '@rocket.chat/core-typings'; +import type { AtLeast, ILivechatContact, IOmnichannelQueue, IOmnichannelRoom } from '@rocket.chat/core-typings'; import { License } from '@rocket.chat/license'; +import { LivechatContacts } from '@rocket.chat/models'; import moment from 'moment'; +import { isSingleContactEnabled } from '../../../app/livechat/server/lib/Contacts'; import { Livechat } from '../../../app/livechat/server/lib/LivechatTyped'; import { RoutingManager } from '../../../app/livechat/server/lib/RoutingManager'; import { settings } from '../../../app/settings/server'; @@ -60,4 +62,40 @@ export class OmnichannelService extends ServiceClassInternal implements IOmnicha const currentMonth = moment.utc().format('YYYY-MM'); return room.v?.activity?.includes(currentMonth) || !(await License.shouldPreventAction('monthlyActiveContacts')); } + + async isUnverifiedContact(room: AtLeast): Promise { + if (!isSingleContactEnabled() || !room.v.contactId) { + return false; + } + + const contact = await LivechatContacts.findOneById>(room.v.contactId, { + projection: { + _id: 1, + unknown: 1, + channels: 1, + }, + }); + + // Sanity check, should never happen + if (!contact) { + return false; + } + + if (contact.unknown && settings.get('Livechat_Block_Unknown_Contacts')) { + return true; + } + + const isContactVerified = + (contact.channels?.filter((channel) => channel.verified && channel.name === room.source?.type) || []).length > 0; + + if (!isContactVerified && settings.get('Livechat_Block_Unverified_Contacts')) { + return true; + } + + if (!settings.get('Livechat_Request_Verification_On_First_Contact_Only')) { + return true; + } + + return false; + } } diff --git a/packages/core-services/src/types/IOmnichannelService.ts b/packages/core-services/src/types/IOmnichannelService.ts index 73006641c8cd..197dbb28779c 100644 --- a/packages/core-services/src/types/IOmnichannelService.ts +++ b/packages/core-services/src/types/IOmnichannelService.ts @@ -5,4 +5,5 @@ import type { IServiceClass } from './ServiceClass'; export interface IOmnichannelService extends IServiceClass { getQueueWorker(): IOmnichannelQueue; isWithinMACLimit(_room: AtLeast): Promise; + isUnverifiedContact(_room: AtLeast): Promise; } From aa1ad50e08e8ff0c9b902be6f51a672cec9196b0 Mon Sep 17 00:00:00 2001 From: gustrb Date: Tue, 15 Oct 2024 17:45:09 -0300 Subject: [PATCH 14/37] chore: only add to the queue inquiries that can be processed --- .../meteor/app/livechat/server/api/v1/room.ts | 9 --------- .../app/livechat/server/api/v1/videoCall.ts | 8 -------- apps/meteor/app/livechat/server/lib/Helper.ts | 1 - .../app/livechat/server/lib/LivechatTyped.ts | 4 ---- .../app/livechat/server/lib/QueueManager.ts | 19 ++++++++----------- .../app/livechat/server/lib/RoutingManager.ts | 1 + .../server/methods/returnAsInquiry.ts | 6 +----- .../livechat/server/methods/sendTranscript.ts | 4 ---- .../app/livechat/server/methods/transfer.ts | 4 ---- 9 files changed, 10 insertions(+), 46 deletions(-) diff --git a/apps/meteor/app/livechat/server/api/v1/room.ts b/apps/meteor/app/livechat/server/api/v1/room.ts index a54db3496070..7d52617e074a 100644 --- a/apps/meteor/app/livechat/server/api/v1/room.ts +++ b/apps/meteor/app/livechat/server/api/v1/room.ts @@ -301,11 +301,6 @@ API.v1.addRoute( throw new Error('error-mac-limit-reached'); } - if (await Omnichannel.isUnverifiedContact(room)) { - // TODO: register the error - throw new Error('error-unverified-contact'); - } - const guest = await LivechatVisitors.findOneEnabledById(room.v?._id); if (!guest) { throw new Error('error-invalid-visitor'); @@ -360,10 +355,6 @@ API.v1.addRoute( throw new Error('error-mac-limit-reached'); } - if (await Omnichannel.isUnverifiedContact(room)) { - throw new Error('error-unverified-contact'); - } - if (!(await canAccessRoomAsync(room, user))) { throw new Error('error-not-allowed'); } diff --git a/apps/meteor/app/livechat/server/api/v1/videoCall.ts b/apps/meteor/app/livechat/server/api/v1/videoCall.ts index 3361d881c061..dd9c701e6495 100644 --- a/apps/meteor/app/livechat/server/api/v1/videoCall.ts +++ b/apps/meteor/app/livechat/server/api/v1/videoCall.ts @@ -33,10 +33,6 @@ API.v1.addRoute( throw new Error('error-mac-limit-reached'); } - if (await Omnichannel.isUnverifiedContact(room as IOmnichannelRoom)) { - throw new Error('error-unverified-contact'); - } - const webrtcCallingAllowed = rcSettings.get('WebRTC_Enabled') === true && rcSettings.get('Omnichannel_call_provider') === 'WebRTC'; if (!webrtcCallingAllowed) { throw new Error('webRTC calling not enabled'); @@ -99,10 +95,6 @@ API.v1.addRoute( throw new Error('error-mac-limit-reached'); } - if (await Omnichannel.isUnverifiedContact(room as IOmnichannelRoom)) { - throw new Error('error-unverified-contact'); - } - const call = await Messages.findOneById(callId); if (!call || call.t !== 'livechat_webrtc_video_call') { throw new Error('invalid-callId'); diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index 878a65daac50..7bc1c7fa415d 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -618,7 +618,6 @@ export const forwardRoomToDepartment = async (room: IOmnichannelRoom, guest: ILi if ( !RoutingManager.getConfig()?.autoAssignAgent || !(await Omnichannel.isWithinMACLimit(room)) || - (await Omnichannel.isUnverifiedContact(room)) || (department?.allowReceiveForwardOffline && !(await LivechatTyped.checkOnlineAgents(departmentId))) ) { logger.debug(`Room ${room._id} will be on department queue`); diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index 32924773f17e..d4863d8ef1b8 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -1478,10 +1478,6 @@ class LivechatClass { throw new Error('error-mac-limit-reached'); } - if (await Omnichannel.isUnverifiedContact(room as IOmnichannelRoom)) { - throw new Error('error-unverified-contact'); - } - const { _id, username, name, utcOffset } = user; const transcriptRequest = { requestedAt: new Date(), diff --git a/apps/meteor/app/livechat/server/lib/QueueManager.ts b/apps/meteor/app/livechat/server/lib/QueueManager.ts index f2a01aa836be..b27f229185c7 100644 --- a/apps/meteor/app/livechat/server/lib/QueueManager.ts +++ b/apps/meteor/app/livechat/server/lib/QueueManager.ts @@ -93,12 +93,6 @@ export class QueueManager { return; } - if (inquiry.v.contactId && (await Omnichannel.isUnverifiedContact(room))) { - logger.error({ msg: 'Contact is not verified, not routing inquiry', inquiry }); - await Promise.all([unverifyContactChannel(inquiry.v.contactId, room.source.type, inquiry.v._id), saveQueueInquiry(inquiry)]); - return; - } - const inquiryAgent = await RoutingManager.delegateAgent(defaultAgent, inquiry); logger.debug(`Delegating inquiry with id ${inquiry._id} to agent ${defaultAgent?.username}`); await callbacks.run('livechat.beforeRouteChat', inquiry, inquiryAgent); @@ -129,10 +123,6 @@ export class QueueManager { return LivechatInquiryStatus.QUEUED; } - if (await Omnichannel.isUnverifiedContact(room)) { - return LivechatInquiryStatus.QUEUED; - } - if (RoutingManager.getConfig()?.autoAssignAgent) { return LivechatInquiryStatus.READY; } @@ -267,7 +257,14 @@ export class QueueManager { void notifyOnSettingChanged(livechatSetting); } - const newRoom = (await this.queueInquiry(inquiry, room, defaultAgent)) ?? (await LivechatRooms.findOneById(rid)); + let newRoom; + if (await Omnichannel.isUnverifiedContact(room)) { + newRoom = await LivechatRooms.findOneById(rid); + await unverifyContactChannel(inquiry.v.contactId, room.source.type, inquiry.v._id); + } else { + newRoom = (await this.queueInquiry(inquiry, room, defaultAgent)) ?? (await LivechatRooms.findOneById(rid)); + } + if (!newRoom) { logger.error(`Room with id ${rid} not found`); throw new Error('room-not-found'); diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index 28e5c72efc16..1fe551c7af5c 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -107,6 +107,7 @@ export const RoutingManager: Routing = { async delegateInquiry(inquiry, agent, options = {}, room) { const { department, rid } = inquiry; logger.debug(`Attempting to delegate inquiry ${inquiry._id}`); + if (!agent || (agent.username && !(await Users.findOneOnlineAgentByUserList(agent.username)) && !(await allowAgentSkipQueue(agent)))) { logger.debug(`Agent offline or invalid. Using routing method to get next agent for inquiry ${inquiry._id}`); agent = await this.getNextAgent(department); diff --git a/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts b/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts index 6e8c43c438eb..5101f4119b68 100644 --- a/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts +++ b/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts @@ -34,11 +34,7 @@ Meteor.methods({ throw new Meteor.Error('error-mac-limit-reached', 'MAC limit reached', { method: 'livechat:returnAsInquiry' }); } - if (await Omnichannel.isUnverifiedContact(room)) { - throw new Meteor.Error('error-unverified-contact', 'Unverified contact', { method: 'livechat:returnAsInquiry' }); - } - - if (!room.open) { + if (!room.open) { throw new Meteor.Error('room-closed', 'Room closed', { method: 'livechat:returnAsInquiry' }); } diff --git a/apps/meteor/app/livechat/server/methods/sendTranscript.ts b/apps/meteor/app/livechat/server/methods/sendTranscript.ts index e06707d38d60..00287fa89327 100644 --- a/apps/meteor/app/livechat/server/methods/sendTranscript.ts +++ b/apps/meteor/app/livechat/server/methods/sendTranscript.ts @@ -39,10 +39,6 @@ Meteor.methods({ throw new Meteor.Error('error-mac-limit-reached', 'MAC limit reached', { method: 'livechat:sendTranscript' }); } - if (await Omnichannel.isUnverifiedContact(room)) { - throw new Meteor.Error('error-unverified-contact', 'Unverified contact', { method: 'livechat:sendTranscript' }); - } - return sendTranscript({ token, rid, email, subject, user }); }, }); diff --git a/apps/meteor/app/livechat/server/methods/transfer.ts b/apps/meteor/app/livechat/server/methods/transfer.ts index 0dbde5690aec..e0516fa27981 100644 --- a/apps/meteor/app/livechat/server/methods/transfer.ts +++ b/apps/meteor/app/livechat/server/methods/transfer.ts @@ -54,10 +54,6 @@ Meteor.methods({ throw new Meteor.Error('error-mac-limit-reached', 'MAC limit reached', { method: 'livechat:transfer' }); } - if (await Omnichannel.isUnverifiedContact(room)) { - throw new Meteor.Error('error-unverified-contact', 'Unverified contact', { method: 'livechat:transfer' }); - } - const subscription = await Subscriptions.findOneByRoomIdAndUserId(room._id, uid, { projection: { _id: 1 }, }); From 67e084d0f9b2dc1faee7f81943ed841d0d88032b Mon Sep 17 00:00:00 2001 From: gustrb Date: Tue, 15 Oct 2024 18:05:01 -0300 Subject: [PATCH 15/37] chore: improve docs --- apps/meteor/app/livechat/server/lib/QueueManager.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/meteor/app/livechat/server/lib/QueueManager.ts b/apps/meteor/app/livechat/server/lib/QueueManager.ts index b27f229185c7..dd728255c39c 100644 --- a/apps/meteor/app/livechat/server/lib/QueueManager.ts +++ b/apps/meteor/app/livechat/server/lib/QueueManager.ts @@ -258,9 +258,12 @@ export class QueueManager { } let newRoom; + // Note: if the contact is not verified, we cannot add it to the queue, since no agent should be able to pick the conversation + // up. So if the contact is not verified we don't add it to queue and wait an event to be sent to properly add it to queue if (await Omnichannel.isUnverifiedContact(room)) { newRoom = await LivechatRooms.findOneById(rid); await unverifyContactChannel(inquiry.v.contactId, room.source.type, inquiry.v._id); + // void Apps.self?.triggerEvent(AppEvents.IPostContactVerificationAppAssigned, inquiry.v, room); } else { newRoom = (await this.queueInquiry(inquiry, room, defaultAgent)) ?? (await LivechatRooms.findOneById(rid)); } From baea9d52923d03d534e3df829b65fb154aedd116 Mon Sep 17 00:00:00 2001 From: gustrb Date: Tue, 15 Oct 2024 18:21:22 -0300 Subject: [PATCH 16/37] chore: lint-fix --- apps/meteor/app/livechat/server/methods/returnAsInquiry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts b/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts index 5101f4119b68..bf76519a5afb 100644 --- a/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts +++ b/apps/meteor/app/livechat/server/methods/returnAsInquiry.ts @@ -34,7 +34,7 @@ Meteor.methods({ throw new Meteor.Error('error-mac-limit-reached', 'MAC limit reached', { method: 'livechat:returnAsInquiry' }); } - if (!room.open) { + if (!room.open) { throw new Meteor.Error('room-closed', 'Room closed', { method: 'livechat:returnAsInquiry' }); } From eea01d1d76b2b9ac8095219e765f8f73bac09003 Mon Sep 17 00:00:00 2001 From: gustrb Date: Tue, 15 Oct 2024 18:59:55 -0300 Subject: [PATCH 17/37] fix: remove the contactId from the ILivechatVisitor --- apps/meteor/app/livechat/server/lib/Helper.ts | 6 ++++-- packages/core-typings/src/IInquiry.ts | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index 7bc1c7fa415d..0e29f424069e 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -168,7 +168,7 @@ export const createLivechatInquiry = async ({ }: { rid: string; name?: string; - guest?: Pick; + guest?: Pick; message?: string; initialStatus?: LivechatInquiryStatus; extraData?: Pick; @@ -188,7 +188,7 @@ export const createLivechatInquiry = async ({ const extraInquiryInfo = await callbacks.run('livechat.beforeInquiry', extraData); - const { _id, username, token, department, status = UserStatus.ONLINE, activity, contactId } = guest; + const { _id, username, token, department, status = UserStatus.ONLINE, activity } = guest; const ts = new Date(); @@ -197,6 +197,8 @@ export const createLivechatInquiry = async ({ visitor: { _id, username, department, status, activity }, }); + const contactId = await getContactIdByVisitorId(_id); + const result = await LivechatInquiry.findOneAndUpdate( { rid, diff --git a/packages/core-typings/src/IInquiry.ts b/packages/core-typings/src/IInquiry.ts index aed6ffd28701..ec1a4df50c46 100644 --- a/packages/core-typings/src/IInquiry.ts +++ b/packages/core-typings/src/IInquiry.ts @@ -37,8 +37,9 @@ export interface ILivechatInquiryRecord extends IRocketChatRecord { ts: Date; message: string; status: LivechatInquiryStatus; - v: Pick & { + v: Pick & { lastMessageTs?: Date; + contactId: string; }; t: 'l'; From 281e819dace133d2baee4ad5f25fecbbc5deadea Mon Sep 17 00:00:00 2001 From: gustrb Date: Tue, 15 Oct 2024 19:38:56 -0300 Subject: [PATCH 18/37] feat: add method to search inquiry by contactId --- apps/meteor/server/models/raw/LivechatInquiry.ts | 9 +++++++++ packages/core-typings/src/IInquiry.ts | 2 +- .../model-typings/src/models/ILivechatInquiryModel.ts | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/meteor/server/models/raw/LivechatInquiry.ts b/apps/meteor/server/models/raw/LivechatInquiry.ts index 6746d6f87926..a4d6adfb82f1 100644 --- a/apps/meteor/server/models/raw/LivechatInquiry.ts +++ b/apps/meteor/server/models/raw/LivechatInquiry.ts @@ -411,6 +411,15 @@ export class LivechatInquiryRaw extends BaseRaw implemen return this.findOne(query); } + findOneByContactId(contactId: string): Promise { + const query: Filter = { + 'v.contactId': contactId, + 'status': LivechatInquiryStatus.READY, + }; + + return this.findOne(query); + } + removeDefaultAgentById(inquiryId: string): Promise { return this.updateOne( { diff --git a/packages/core-typings/src/IInquiry.ts b/packages/core-typings/src/IInquiry.ts index ec1a4df50c46..265e581c4ace 100644 --- a/packages/core-typings/src/IInquiry.ts +++ b/packages/core-typings/src/IInquiry.ts @@ -39,7 +39,7 @@ export interface ILivechatInquiryRecord extends IRocketChatRecord { status: LivechatInquiryStatus; v: Pick & { lastMessageTs?: Date; - contactId: string; + contactId?: string; }; t: 'l'; diff --git a/packages/model-typings/src/models/ILivechatInquiryModel.ts b/packages/model-typings/src/models/ILivechatInquiryModel.ts index 3535b65c3518..3f0d4d71c6a8 100644 --- a/packages/model-typings/src/models/ILivechatInquiryModel.ts +++ b/packages/model-typings/src/models/ILivechatInquiryModel.ts @@ -43,4 +43,5 @@ export interface ILivechatInquiryModel extends IBaseModel; markInquiryActiveForPeriod(rid: ILivechatInquiryRecord['rid'], period: string): Promise; findIdsByVisitorToken(token: ILivechatInquiryRecord['v']['token']): FindCursor; + findOneByContactId(contactId: string): Promise; } From d2ed619a9b612559d4841977ae4819858e61d77b Mon Sep 17 00:00:00 2001 From: gustrb Date: Tue, 15 Oct 2024 20:05:27 -0300 Subject: [PATCH 19/37] chore: channels were already being created --- .../app/livechat/server/lib/LivechatTyped.ts | 49 ------------------- 1 file changed, 49 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index d4863d8ef1b8..b43edaae1f15 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -446,55 +446,6 @@ class LivechatClass { } } - if (isSingleContactEnabled()) { - let { contactId } = visitor; - - if (!contactId) { - const visitorContact = await LivechatVisitors.findOne< - Pick - >(visitor._id, { - projection: { - name: 1, - contactManager: 1, - livechatData: 1, - phone: 1, - visitorEmails: 1, - username: 1, - contactId: 1, - }, - }); - - contactId = visitorContact?.contactId; - } - - if (!contactId) { - // ensure that old visitors have a contact - contactId = await createContactFromVisitor(visitor); - } - - const contact = await LivechatContacts.findOneById>(contactId, { - projection: { _id: 1, channels: 1 }, - }); - - if (contact) { - const channel = contact.channels?.find( - (channel: ILivechatContactChannel) => channel.name === roomInfo.source?.type && channel.visitorId === visitor._id, - ); - - if (!channel) { - Livechat.logger.debug(`Adding channel for contact ${contact._id}`); - - await LivechatContacts.addChannel(contact._id, { - name: roomInfo.source?.label || roomInfo.source?.type.toString() || OmnichannelSourceType.OTHER, - visitorId: visitor._id, - blocked: false, - verified: false, - details: roomInfo.source, - }); - } - } - } - // delegate room creation to QueueManager Livechat.logger.debug(`Calling QueueManager to request a room for visitor ${visitor._id}`); From 3387e519ffb637d30050b460b30b177efcd11ace Mon Sep 17 00:00:00 2001 From: gustrb Date: Tue, 15 Oct 2024 20:33:23 -0300 Subject: [PATCH 20/37] feat: move contact verification livechat settings to EE --- .../server/services/omnichannel.ts | 45 +++++++++++++++++++ .../server/services/omnichannel/service.ts | 38 +--------------- 2 files changed, 47 insertions(+), 36 deletions(-) create mode 100644 apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.ts diff --git a/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.ts b/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.ts new file mode 100644 index 000000000000..b6396ea61292 --- /dev/null +++ b/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.ts @@ -0,0 +1,45 @@ +import type { AtLeast, ILivechatContact, IOmnichannelRoom } from '@rocket.chat/core-typings'; +import { License } from '@rocket.chat/license'; +import { LivechatContacts } from '@rocket.chat/models'; + +import { settings } from '../../../../../app/settings/server'; +import { OmnichannelService } from '../../../../../server/services/omnichannel/service'; + +// TODO: add correct license: 'chat.rocket.contact-id-verification' +await License.overwriteClassOnLicense('livechat-enterprise', OmnichannelService, { + async isUnverifiedContact(room: AtLeast): Promise { + if (!room.v.contactId) { + return false; + } + + const contact = await LivechatContacts.findOneById>(room.v.contactId, { + projection: { + _id: 1, + unknown: 1, + channels: 1, + }, + }); + + // Sanity check, should never happen + if (!contact) { + return false; + } + + if (contact.unknown && settings.get('Livechat_Block_Unknown_Contacts')) { + return true; + } + + const isContactVerified = + (contact.channels?.filter((channel) => channel.verified && channel.name === room.source?.type) || []).length > 0; + + if (!isContactVerified && settings.get('Livechat_Block_Unverified_Contacts')) { + return true; + } + + if (!settings.get('Livechat_Request_Verification_On_First_Contact_Only')) { + return true; + } + + return false; + }, +}); diff --git a/apps/meteor/server/services/omnichannel/service.ts b/apps/meteor/server/services/omnichannel/service.ts index 32d16118c935..3dcafdae5f7c 100644 --- a/apps/meteor/server/services/omnichannel/service.ts +++ b/apps/meteor/server/services/omnichannel/service.ts @@ -1,11 +1,9 @@ import { ServiceClassInternal } from '@rocket.chat/core-services'; import type { IOmnichannelService } from '@rocket.chat/core-services'; -import type { AtLeast, ILivechatContact, IOmnichannelQueue, IOmnichannelRoom } from '@rocket.chat/core-typings'; +import type { AtLeast, IOmnichannelQueue, IOmnichannelRoom } from '@rocket.chat/core-typings'; import { License } from '@rocket.chat/license'; -import { LivechatContacts } from '@rocket.chat/models'; import moment from 'moment'; -import { isSingleContactEnabled } from '../../../app/livechat/server/lib/Contacts'; import { Livechat } from '../../../app/livechat/server/lib/LivechatTyped'; import { RoutingManager } from '../../../app/livechat/server/lib/RoutingManager'; import { settings } from '../../../app/settings/server'; @@ -63,39 +61,7 @@ export class OmnichannelService extends ServiceClassInternal implements IOmnicha return room.v?.activity?.includes(currentMonth) || !(await License.shouldPreventAction('monthlyActiveContacts')); } - async isUnverifiedContact(room: AtLeast): Promise { - if (!isSingleContactEnabled() || !room.v.contactId) { - return false; - } - - const contact = await LivechatContacts.findOneById>(room.v.contactId, { - projection: { - _id: 1, - unknown: 1, - channels: 1, - }, - }); - - // Sanity check, should never happen - if (!contact) { - return false; - } - - if (contact.unknown && settings.get('Livechat_Block_Unknown_Contacts')) { - return true; - } - - const isContactVerified = - (contact.channels?.filter((channel) => channel.verified && channel.name === room.source?.type) || []).length > 0; - - if (!isContactVerified && settings.get('Livechat_Block_Unverified_Contacts')) { - return true; - } - - if (!settings.get('Livechat_Request_Verification_On_First_Contact_Only')) { - return true; - } - + async isUnverifiedContact(_room: AtLeast): Promise { return false; } } From bd2ebdbecca7ad7724a2ec193199ee3d0bd486ff Mon Sep 17 00:00:00 2001 From: gustrb Date: Tue, 15 Oct 2024 21:16:37 -0300 Subject: [PATCH 21/37] chore: lint-fix --- apps/meteor/app/livechat/server/lib/Helper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index 0e29f424069e..16cd9cf0c79a 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -197,7 +197,7 @@ export const createLivechatInquiry = async ({ visitor: { _id, username, department, status, activity }, }); - const contactId = await getContactIdByVisitorId(_id); + const contactId = await getContactIdByVisitorId(_id); const result = await LivechatInquiry.findOneAndUpdate( { From 7d910d94c8306a9f77f67bdc07640bf396dfffcb Mon Sep 17 00:00:00 2001 From: gustrb Date: Wed, 16 Oct 2024 07:19:10 -0300 Subject: [PATCH 22/37] chore: move service into correct license --- apps/meteor/ee/app/contact-id-verification/server/index.ts | 1 + .../server/services/omnichannelService.ts} | 3 +-- apps/meteor/ee/server/index.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 apps/meteor/ee/app/contact-id-verification/server/index.ts rename apps/meteor/ee/app/{livechat-enterprise/server/services/omnichannel.ts => contact-id-verification/server/services/omnichannelService.ts} (89%) diff --git a/apps/meteor/ee/app/contact-id-verification/server/index.ts b/apps/meteor/ee/app/contact-id-verification/server/index.ts new file mode 100644 index 000000000000..103151b34ea2 --- /dev/null +++ b/apps/meteor/ee/app/contact-id-verification/server/index.ts @@ -0,0 +1 @@ +import './services/omnichannelService'; diff --git a/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.ts b/apps/meteor/ee/app/contact-id-verification/server/services/omnichannelService.ts similarity index 89% rename from apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.ts rename to apps/meteor/ee/app/contact-id-verification/server/services/omnichannelService.ts index b6396ea61292..a26ab6eb63a6 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/services/omnichannel.ts +++ b/apps/meteor/ee/app/contact-id-verification/server/services/omnichannelService.ts @@ -5,8 +5,7 @@ import { LivechatContacts } from '@rocket.chat/models'; import { settings } from '../../../../../app/settings/server'; import { OmnichannelService } from '../../../../../server/services/omnichannel/service'; -// TODO: add correct license: 'chat.rocket.contact-id-verification' -await License.overwriteClassOnLicense('livechat-enterprise', OmnichannelService, { +await License.overwriteClassOnLicense('chat.rocket.contact-id-verification', OmnichannelService, { async isUnverifiedContact(room: AtLeast): Promise { if (!room.v.contactId) { return false; diff --git a/apps/meteor/ee/server/index.ts b/apps/meteor/ee/server/index.ts index 744de6f0c3ca..e81bb032b01f 100644 --- a/apps/meteor/ee/server/index.ts +++ b/apps/meteor/ee/server/index.ts @@ -6,6 +6,7 @@ import '../app/canned-responses/server/index'; import '../app/livechat-enterprise/server/index'; import '../app/message-read-receipt/server/index'; import '../app/voip-enterprise/server/index'; +import '../app/contact-id-verification/server/index'; import '../app/settings/server/index'; import './api'; import './requestSeatsRoute'; From 85d1e4dbd935ffe1f4aa0ad58aeefd5a715a406d Mon Sep 17 00:00:00 2001 From: gustrb Date: Wed, 16 Oct 2024 15:37:02 -0300 Subject: [PATCH 23/37] chore: change license --- .../server/services/omnichannelService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/ee/app/contact-id-verification/server/services/omnichannelService.ts b/apps/meteor/ee/app/contact-id-verification/server/services/omnichannelService.ts index a26ab6eb63a6..9f648527cb81 100644 --- a/apps/meteor/ee/app/contact-id-verification/server/services/omnichannelService.ts +++ b/apps/meteor/ee/app/contact-id-verification/server/services/omnichannelService.ts @@ -5,7 +5,7 @@ import { LivechatContacts } from '@rocket.chat/models'; import { settings } from '../../../../../app/settings/server'; import { OmnichannelService } from '../../../../../server/services/omnichannel/service'; -await License.overwriteClassOnLicense('chat.rocket.contact-id-verification', OmnichannelService, { +await License.overwriteClassOnLicense('contact-id-verification', OmnichannelService, { async isUnverifiedContact(room: AtLeast): Promise { if (!room.v.contactId) { return false; From 58d6aa97d2f773d52bee49b0dad0a4a4d437216a Mon Sep 17 00:00:00 2001 From: gustrb Date: Wed, 16 Oct 2024 16:28:45 -0300 Subject: [PATCH 24/37] chore: remove settings --- .../livechat-enterprise/server/settings.ts | 43 +------------------ 1 file changed, 1 insertion(+), 42 deletions(-) diff --git a/apps/meteor/ee/app/livechat-enterprise/server/settings.ts b/apps/meteor/ee/app/livechat-enterprise/server/settings.ts index 304d46381837..9b701f3dabad 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/settings.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/settings.ts @@ -190,7 +190,7 @@ export const createSettings = async (): Promise => { type: 'select', public: true, hidden: true, - values: [{ key: 'VerifyChat', i18nLabel: 'VerifyChat' }], + values: [{ key: 'verifychat', i18nLabel: 'VerifyChat' }], enableQuery: omnichannelEnabledQuery, invalidValue: '', section: 'Contact_identification', @@ -208,47 +208,6 @@ export const createSettings = async (): Promise => { modules: ['livechat-enterprise'], }); }); - - await this.add('Livechat_Block_Unknown_Contacts', false, { - type: 'boolean', - public: true, - enableQuery: omnichannelEnabledQuery, - invalidValue: false, - section: 'Contact_identification', - enterprise: true, - modules: ['livechat-enterprise'], - }); - - await this.add('Livechat_Block_Unverified_Contacts', false, { - type: 'boolean', - public: true, - enableQuery: omnichannelEnabledQuery, - invalidValue: false, - section: 'Contact_identification', - enterprise: true, - modules: ['livechat-enterprise'], - }); - - await this.add('Livechat_Contact_Verification_App', '', { - type: 'select', - public: true, - values: [{ key: 'VerifyChat', i18nLabel: 'VerifyChat' }], - enableQuery: omnichannelEnabledQuery, - invalidValue: '', - section: 'Contact_identification', - enterprise: true, - modules: ['livechat-enterprise'], - }); - - await this.add('Livechat_Request_Verification_On_First_Contact_Only', false, { - type: 'boolean', - public: true, - enableQuery: omnichannelEnabledQuery, - invalidValue: false, - section: 'Contact_identification', - enterprise: true, - modules: ['livechat-enterprise'], - }); }); await settingsRegistry.add('Livechat_AdditionalWidgetScripts', '', { From 0d26dbaed5c46c3cd8dfe626e0ba1656892dcaac Mon Sep 17 00:00:00 2001 From: gustrb Date: Wed, 16 Oct 2024 16:29:56 -0300 Subject: [PATCH 25/37] chore: remove right settings --- .../livechat-enterprise/server/settings.ts | 72 +++++++++++-------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/apps/meteor/ee/app/livechat-enterprise/server/settings.ts b/apps/meteor/ee/app/livechat-enterprise/server/settings.ts index 9b701f3dabad..6a230aad7c4d 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/settings.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/settings.ts @@ -176,37 +176,47 @@ export const createSettings = async (): Promise => { enterprise: true, modules: ['livechat-enterprise'], }); - await this.add('Livechat_Block_Unverified_Contacts', false, { - type: 'boolean', - public: true, - hidden: true, - enableQuery: omnichannelEnabledQuery, - invalidValue: false, - section: 'Contact_identification', - enterprise: true, - modules: ['livechat-enterprise'], - }); - await this.add('Livechat_Contact_Verification_App', '', { - type: 'select', - public: true, - hidden: true, - values: [{ key: 'verifychat', i18nLabel: 'VerifyChat' }], - enableQuery: omnichannelEnabledQuery, - invalidValue: '', - section: 'Contact_identification', - enterprise: true, - modules: ['livechat-enterprise'], - }); - await this.add('Livechat_Request_Verification_On_First_Contact_Only', false, { - type: 'boolean', - public: true, - hidden: true, - enableQuery: omnichannelEnabledQuery, - invalidValue: false, - section: 'Contact_identification', - enterprise: true, - modules: ['livechat-enterprise'], - }); + }); + + await this.add('Livechat_Block_Unknown_Contacts', false, { + type: 'boolean', + public: true, + enableQuery: omnichannelEnabledQuery, + invalidValue: false, + section: 'Contact_identification', + enterprise: true, + modules: ['livechat-enterprise'], + }); + + await this.add('Livechat_Block_Unverified_Contacts', false, { + type: 'boolean', + public: true, + enableQuery: omnichannelEnabledQuery, + invalidValue: false, + section: 'Contact_identification', + enterprise: true, + modules: ['livechat-enterprise'], + }); + + await this.add('Livechat_Contact_Verification_App', '', { + type: 'select', + public: true, + values: [{ key: 'VerifyChat', i18nLabel: 'VerifyChat' }], + enableQuery: omnichannelEnabledQuery, + invalidValue: '', + section: 'Contact_identification', + enterprise: true, + modules: ['livechat-enterprise'], + }); + + await this.add('Livechat_Request_Verification_On_First_Contact_Only', false, { + type: 'boolean', + public: true, + enableQuery: omnichannelEnabledQuery, + invalidValue: false, + section: 'Contact_identification', + enterprise: true, + modules: ['livechat-enterprise'], }); }); From 36d3718b9dcb81b8f00f44a23925518466fbccfb Mon Sep 17 00:00:00 2001 From: gustrb Date: Wed, 16 Oct 2024 16:30:47 -0300 Subject: [PATCH 26/37] chore: remove setting --- .../ee/app/livechat-enterprise/server/settings.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/apps/meteor/ee/app/livechat-enterprise/server/settings.ts b/apps/meteor/ee/app/livechat-enterprise/server/settings.ts index 6a230aad7c4d..38023db73b11 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/settings.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/settings.ts @@ -165,17 +165,6 @@ export const createSettings = async (): Promise => { modules: ['livechat-enterprise'], invalidValue: '', }); - - await this.add('Livechat_Block_Unknown_Contacts', false, { - type: 'boolean', - public: true, - hidden: true, - enableQuery: omnichannelEnabledQuery, - invalidValue: false, - section: 'Contact_identification', - enterprise: true, - modules: ['livechat-enterprise'], - }); }); await this.add('Livechat_Block_Unknown_Contacts', false, { From e8ba3ca8b2b67f3e632eb998bd8e84e56ade6daa Mon Sep 17 00:00:00 2001 From: gustrb Date: Wed, 16 Oct 2024 17:14:18 -0300 Subject: [PATCH 27/37] chore: use makeFunction and patch --- .../app/livechat/server/lib/Contacts.ts | 2 + .../app/livechat/server/lib/QueueManager.ts | 4 +- .../livechat/server/methods/takeInquiry.ts | 3 +- .../contact-id-verification/server/index.ts | 1 - .../server/services/omnichannelService.ts | 44 ----------------- apps/meteor/ee/server/patches/index.ts | 1 + .../ee/server/patches/isUnverifiedContact.ts | 48 +++++++++++++++++++ .../server/services/omnichannel/service.ts | 4 -- .../src/types/IOmnichannelService.ts | 1 - 9 files changed, 55 insertions(+), 53 deletions(-) delete mode 100644 apps/meteor/ee/app/contact-id-verification/server/index.ts delete mode 100644 apps/meteor/ee/app/contact-id-verification/server/services/omnichannelService.ts create mode 100644 apps/meteor/ee/server/patches/isUnverifiedContact.ts diff --git a/apps/meteor/app/livechat/server/lib/Contacts.ts b/apps/meteor/app/livechat/server/lib/Contacts.ts index a7008a5325c6..6f0c1466bb2a 100644 --- a/apps/meteor/app/livechat/server/lib/Contacts.ts +++ b/apps/meteor/app/livechat/server/lib/Contacts.ts @@ -541,3 +541,5 @@ export async function validateContactManager(contactManagerUserId: string) { export const verifyContactChannel = makeFunction(async (_params: VerifyContactChannelParams): Promise => null); export const mergeContacts = makeFunction(async (_contactId: string, _visitorId: string): Promise => null); + +export const isUnverifiedContact = makeFunction(async (_room: AtLeast): Promise => false); diff --git a/apps/meteor/app/livechat/server/lib/QueueManager.ts b/apps/meteor/app/livechat/server/lib/QueueManager.ts index dd728255c39c..6e1c15c72e6e 100644 --- a/apps/meteor/app/livechat/server/lib/QueueManager.ts +++ b/apps/meteor/app/livechat/server/lib/QueueManager.ts @@ -25,7 +25,7 @@ import { } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; import { i18n } from '../../../utils/lib/i18n'; -import { unverifyContactChannel } from './Contacts'; +import { isUnverifiedContact, unverifyContactChannel } from './Contacts'; import { createLivechatRoom, createLivechatInquiry, allowAgentSkipQueue } from './Helper'; import { Livechat } from './LivechatTyped'; import { RoutingManager } from './RoutingManager'; @@ -260,7 +260,7 @@ export class QueueManager { let newRoom; // Note: if the contact is not verified, we cannot add it to the queue, since no agent should be able to pick the conversation // up. So if the contact is not verified we don't add it to queue and wait an event to be sent to properly add it to queue - if (await Omnichannel.isUnverifiedContact(room)) { + if (await isUnverifiedContact(room)) { newRoom = await LivechatRooms.findOneById(rid); await unverifyContactChannel(inquiry.v.contactId, room.source.type, inquiry.v._id); // void Apps.self?.triggerEvent(AppEvents.IPostContactVerificationAppAssigned, inquiry.v, room); diff --git a/apps/meteor/app/livechat/server/methods/takeInquiry.ts b/apps/meteor/app/livechat/server/methods/takeInquiry.ts index d04f387a2460..229314a4d219 100644 --- a/apps/meteor/app/livechat/server/methods/takeInquiry.ts +++ b/apps/meteor/app/livechat/server/methods/takeInquiry.ts @@ -6,6 +6,7 @@ import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { settings } from '../../../settings/server'; import { RoutingManager } from '../lib/RoutingManager'; +import { isUnverifiedContact } from '../lib/Contacts'; declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -54,7 +55,7 @@ export const takeInquiry = async ( throw new Error('error-mac-limit-reached'); } - if (await Omnichannel.isUnverifiedContact(room)) { + if (await isUnverifiedContact(room)) { throw new Error('error-unverified-contact'); } diff --git a/apps/meteor/ee/app/contact-id-verification/server/index.ts b/apps/meteor/ee/app/contact-id-verification/server/index.ts deleted file mode 100644 index 103151b34ea2..000000000000 --- a/apps/meteor/ee/app/contact-id-verification/server/index.ts +++ /dev/null @@ -1 +0,0 @@ -import './services/omnichannelService'; diff --git a/apps/meteor/ee/app/contact-id-verification/server/services/omnichannelService.ts b/apps/meteor/ee/app/contact-id-verification/server/services/omnichannelService.ts deleted file mode 100644 index 9f648527cb81..000000000000 --- a/apps/meteor/ee/app/contact-id-verification/server/services/omnichannelService.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { AtLeast, ILivechatContact, IOmnichannelRoom } from '@rocket.chat/core-typings'; -import { License } from '@rocket.chat/license'; -import { LivechatContacts } from '@rocket.chat/models'; - -import { settings } from '../../../../../app/settings/server'; -import { OmnichannelService } from '../../../../../server/services/omnichannel/service'; - -await License.overwriteClassOnLicense('contact-id-verification', OmnichannelService, { - async isUnverifiedContact(room: AtLeast): Promise { - if (!room.v.contactId) { - return false; - } - - const contact = await LivechatContacts.findOneById>(room.v.contactId, { - projection: { - _id: 1, - unknown: 1, - channels: 1, - }, - }); - - // Sanity check, should never happen - if (!contact) { - return false; - } - - if (contact.unknown && settings.get('Livechat_Block_Unknown_Contacts')) { - return true; - } - - const isContactVerified = - (contact.channels?.filter((channel) => channel.verified && channel.name === room.source?.type) || []).length > 0; - - if (!isContactVerified && settings.get('Livechat_Block_Unverified_Contacts')) { - return true; - } - - if (!settings.get('Livechat_Request_Verification_On_First_Contact_Only')) { - return true; - } - - return false; - }, -}); diff --git a/apps/meteor/ee/server/patches/index.ts b/apps/meteor/ee/server/patches/index.ts index 49f84bfbf479..e71df230bb9b 100644 --- a/apps/meteor/ee/server/patches/index.ts +++ b/apps/meteor/ee/server/patches/index.ts @@ -3,3 +3,4 @@ import './getInstanceList'; import './isDepartmentCreationAvailable'; import './verifyContactChannel'; import './mergeContacts'; +import './isUnverifiedContact'; diff --git a/apps/meteor/ee/server/patches/isUnverifiedContact.ts b/apps/meteor/ee/server/patches/isUnverifiedContact.ts new file mode 100644 index 000000000000..82ccf8b774c7 --- /dev/null +++ b/apps/meteor/ee/server/patches/isUnverifiedContact.ts @@ -0,0 +1,48 @@ +import type { AtLeast, ILivechatContact, IOmnichannelRoom } from '@rocket.chat/core-typings'; +import { LivechatContacts } from '@rocket.chat/models'; + +import { hasLicense } from '../../../app/license/client'; +import { isUnverifiedContact } from '../../../app/livechat/server/lib/Contacts'; +import { settings } from '../../../app/settings/server'; + +isUnverifiedContact.patch(async (_next, room: AtLeast): Promise => { + const hasContactIdLicense = await hasLicense('contact-id-verification'); + + if (hasContactIdLicense) { + return false; + } + + if (!room.v.contactId) { + return false; + } + + const contact = await LivechatContacts.findOneById>(room.v.contactId, { + projection: { + _id: 1, + unknown: 1, + channels: 1, + }, + }); + + // Sanity check, should never happen + if (!contact) { + return false; + } + + if (contact.unknown && settings.get('Livechat_Block_Unknown_Contacts')) { + return true; + } + + const isContactVerified = + (contact.channels?.filter((channel) => channel.verified && channel.name === room.source?.type) || []).length > 0; + + if (!isContactVerified && settings.get('Livechat_Block_Unverified_Contacts')) { + return true; + } + + if (!settings.get('Livechat_Request_Verification_On_First_Contact_Only')) { + return true; + } + + return false; +}); diff --git a/apps/meteor/server/services/omnichannel/service.ts b/apps/meteor/server/services/omnichannel/service.ts index 3dcafdae5f7c..34599cbc7a33 100644 --- a/apps/meteor/server/services/omnichannel/service.ts +++ b/apps/meteor/server/services/omnichannel/service.ts @@ -60,8 +60,4 @@ export class OmnichannelService extends ServiceClassInternal implements IOmnicha const currentMonth = moment.utc().format('YYYY-MM'); return room.v?.activity?.includes(currentMonth) || !(await License.shouldPreventAction('monthlyActiveContacts')); } - - async isUnverifiedContact(_room: AtLeast): Promise { - return false; - } } diff --git a/packages/core-services/src/types/IOmnichannelService.ts b/packages/core-services/src/types/IOmnichannelService.ts index 197dbb28779c..73006641c8cd 100644 --- a/packages/core-services/src/types/IOmnichannelService.ts +++ b/packages/core-services/src/types/IOmnichannelService.ts @@ -5,5 +5,4 @@ import type { IServiceClass } from './ServiceClass'; export interface IOmnichannelService extends IServiceClass { getQueueWorker(): IOmnichannelQueue; isWithinMACLimit(_room: AtLeast): Promise; - isUnverifiedContact(_room: AtLeast): Promise; } From 771901e3c9f6762e32a0aec98e4893b63db503d4 Mon Sep 17 00:00:00 2001 From: gustrb Date: Thu, 17 Oct 2024 09:38:35 -0300 Subject: [PATCH 28/37] chore: removing unused import --- apps/meteor/ee/server/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/meteor/ee/server/index.ts b/apps/meteor/ee/server/index.ts index e81bb032b01f..744de6f0c3ca 100644 --- a/apps/meteor/ee/server/index.ts +++ b/apps/meteor/ee/server/index.ts @@ -6,7 +6,6 @@ import '../app/canned-responses/server/index'; import '../app/livechat-enterprise/server/index'; import '../app/message-read-receipt/server/index'; import '../app/voip-enterprise/server/index'; -import '../app/contact-id-verification/server/index'; import '../app/settings/server/index'; import './api'; import './requestSeatsRoute'; From c749bb1a7bb8651b58ee023c55781b1656aacf2b Mon Sep 17 00:00:00 2001 From: gustrb Date: Thu, 17 Oct 2024 10:37:24 -0300 Subject: [PATCH 29/37] chore: apply suggestions --- apps/meteor/app/livechat/server/lib/Contacts.ts | 4 +++- .../app/livechat/server/lib/QueueManager.ts | 4 ++-- .../app/livechat/server/methods/takeInquiry.ts | 12 ++++++++++-- apps/meteor/ee/server/patches/index.ts | 2 +- ...Contact.ts => shouldTriggerVerificationApp.ts} | 15 +++++---------- apps/meteor/server/models/raw/LivechatInquiry.ts | 2 +- .../src/models/ILivechatInquiryModel.ts | 2 +- 7 files changed, 23 insertions(+), 18 deletions(-) rename apps/meteor/ee/server/patches/{isUnverifiedContact.ts => shouldTriggerVerificationApp.ts} (61%) diff --git a/apps/meteor/app/livechat/server/lib/Contacts.ts b/apps/meteor/app/livechat/server/lib/Contacts.ts index 6f0c1466bb2a..82944469a859 100644 --- a/apps/meteor/app/livechat/server/lib/Contacts.ts +++ b/apps/meteor/app/livechat/server/lib/Contacts.ts @@ -542,4 +542,6 @@ export const verifyContactChannel = makeFunction(async (_params: VerifyContactCh export const mergeContacts = makeFunction(async (_contactId: string, _visitorId: string): Promise => null); -export const isUnverifiedContact = makeFunction(async (_room: AtLeast): Promise => false); +export const shouldTriggerVerificationApp = makeFunction( + async (_contactId: ILivechatContact['_id'], _source: IOmnichannelSource): Promise => false, +); diff --git a/apps/meteor/app/livechat/server/lib/QueueManager.ts b/apps/meteor/app/livechat/server/lib/QueueManager.ts index 6e1c15c72e6e..f6e7123a19bf 100644 --- a/apps/meteor/app/livechat/server/lib/QueueManager.ts +++ b/apps/meteor/app/livechat/server/lib/QueueManager.ts @@ -25,7 +25,7 @@ import { } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; import { i18n } from '../../../utils/lib/i18n'; -import { isUnverifiedContact, unverifyContactChannel } from './Contacts'; +import { shouldTriggerVerificationApp, unverifyContactChannel } from './Contacts'; import { createLivechatRoom, createLivechatInquiry, allowAgentSkipQueue } from './Helper'; import { Livechat } from './LivechatTyped'; import { RoutingManager } from './RoutingManager'; @@ -260,7 +260,7 @@ export class QueueManager { let newRoom; // Note: if the contact is not verified, we cannot add it to the queue, since no agent should be able to pick the conversation // up. So if the contact is not verified we don't add it to queue and wait an event to be sent to properly add it to queue - if (await isUnverifiedContact(room)) { + if (inquiry.v.contactId && (await shouldTriggerVerificationApp(inquiry.v.contactId, room.source))) { newRoom = await LivechatRooms.findOneById(rid); await unverifyContactChannel(inquiry.v.contactId, room.source.type, inquiry.v._id); // void Apps.self?.triggerEvent(AppEvents.IPostContactVerificationAppAssigned, inquiry.v, room); diff --git a/apps/meteor/app/livechat/server/methods/takeInquiry.ts b/apps/meteor/app/livechat/server/methods/takeInquiry.ts index 229314a4d219..86c24e71ac20 100644 --- a/apps/meteor/app/livechat/server/methods/takeInquiry.ts +++ b/apps/meteor/app/livechat/server/methods/takeInquiry.ts @@ -5,8 +5,8 @@ import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { settings } from '../../../settings/server'; +import { isSingleContactEnabled, shouldTriggerVerificationApp, migrateVisitorIfMissingContact } from '../lib/Contacts'; import { RoutingManager } from '../lib/RoutingManager'; -import { isUnverifiedContact } from '../lib/Contacts'; declare module '@rocket.chat/ddp-client' { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -55,7 +55,15 @@ export const takeInquiry = async ( throw new Error('error-mac-limit-reached'); } - if (await isUnverifiedContact(room)) { + const contactId = await (async () => { + if (!isSingleContactEnabled()) { + return undefined; + } + + return migrateVisitorIfMissingContact(inquiry.v._id, room.source); + })(); + + if (contactId && (await shouldTriggerVerificationApp(contactId, room.source))) { throw new Error('error-unverified-contact'); } diff --git a/apps/meteor/ee/server/patches/index.ts b/apps/meteor/ee/server/patches/index.ts index e71df230bb9b..e202fa9fdc35 100644 --- a/apps/meteor/ee/server/patches/index.ts +++ b/apps/meteor/ee/server/patches/index.ts @@ -3,4 +3,4 @@ import './getInstanceList'; import './isDepartmentCreationAvailable'; import './verifyContactChannel'; import './mergeContacts'; -import './isUnverifiedContact'; +import './shouldTriggerVerificationApp'; diff --git a/apps/meteor/ee/server/patches/isUnverifiedContact.ts b/apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts similarity index 61% rename from apps/meteor/ee/server/patches/isUnverifiedContact.ts rename to apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts index 82ccf8b774c7..2c46388d5e4f 100644 --- a/apps/meteor/ee/server/patches/isUnverifiedContact.ts +++ b/apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts @@ -1,22 +1,18 @@ -import type { AtLeast, ILivechatContact, IOmnichannelRoom } from '@rocket.chat/core-typings'; +import type { IOmnichannelSource, ILivechatContact } from '@rocket.chat/core-typings'; import { LivechatContacts } from '@rocket.chat/models'; import { hasLicense } from '../../../app/license/client'; -import { isUnverifiedContact } from '../../../app/livechat/server/lib/Contacts'; +import { shouldTriggerVerificationApp } from '../../../app/livechat/server/lib/Contacts'; import { settings } from '../../../app/settings/server'; -isUnverifiedContact.patch(async (_next, room: AtLeast): Promise => { +shouldTriggerVerificationApp.patch(async (_next, contactId: ILivechatContact['_id'], source: IOmnichannelSource): Promise => { const hasContactIdLicense = await hasLicense('contact-id-verification'); if (hasContactIdLicense) { return false; } - if (!room.v.contactId) { - return false; - } - - const contact = await LivechatContacts.findOneById>(room.v.contactId, { + const contact = await LivechatContacts.findOneById>(contactId, { projection: { _id: 1, unknown: 1, @@ -33,8 +29,7 @@ isUnverifiedContact.patch(async (_next, room: AtLeast): P return true; } - const isContactVerified = - (contact.channels?.filter((channel) => channel.verified && channel.name === room.source?.type) || []).length > 0; + const isContactVerified = (contact.channels?.filter((channel) => channel.verified && channel.name === source.type) || []).length > 0; if (!isContactVerified && settings.get('Livechat_Block_Unverified_Contacts')) { return true; diff --git a/apps/meteor/server/models/raw/LivechatInquiry.ts b/apps/meteor/server/models/raw/LivechatInquiry.ts index a4d6adfb82f1..1f85be69ed05 100644 --- a/apps/meteor/server/models/raw/LivechatInquiry.ts +++ b/apps/meteor/server/models/raw/LivechatInquiry.ts @@ -411,7 +411,7 @@ export class LivechatInquiryRaw extends BaseRaw implemen return this.findOne(query); } - findOneByContactId(contactId: string): Promise { + findOneReadyByContactId(contactId: string): Promise { const query: Filter = { 'v.contactId': contactId, 'status': LivechatInquiryStatus.READY, diff --git a/packages/model-typings/src/models/ILivechatInquiryModel.ts b/packages/model-typings/src/models/ILivechatInquiryModel.ts index 3f0d4d71c6a8..2a2c368d2d0a 100644 --- a/packages/model-typings/src/models/ILivechatInquiryModel.ts +++ b/packages/model-typings/src/models/ILivechatInquiryModel.ts @@ -43,5 +43,5 @@ export interface ILivechatInquiryModel extends IBaseModel; markInquiryActiveForPeriod(rid: ILivechatInquiryRecord['rid'], period: string): Promise; findIdsByVisitorToken(token: ILivechatInquiryRecord['v']['token']): FindCursor; - findOneByContactId(contactId: string): Promise; + findOneReadyByContactId(contactId: string): Promise; } From e245c1ac8dd7c1c2260d6132c3697aba3bd08747 Mon Sep 17 00:00:00 2001 From: gustrb Date: Thu, 17 Oct 2024 11:02:34 -0300 Subject: [PATCH 30/37] chore: moving unverifyContactChannel to model --- apps/meteor/app/livechat/server/lib/QueueManager.ts | 6 +++--- apps/meteor/server/models/raw/LivechatContacts.ts | 7 +++++++ .../model-typings/src/models/ILivechatContactsModel.ts | 1 + 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/QueueManager.ts b/apps/meteor/app/livechat/server/lib/QueueManager.ts index f6e7123a19bf..179b84da1a23 100644 --- a/apps/meteor/app/livechat/server/lib/QueueManager.ts +++ b/apps/meteor/app/livechat/server/lib/QueueManager.ts @@ -10,7 +10,7 @@ import { type OmnichannelSourceType, } from '@rocket.chat/core-typings'; import { Logger } from '@rocket.chat/logger'; -import { LivechatDepartment, LivechatDepartmentAgents, LivechatInquiry, LivechatRooms, Users } from '@rocket.chat/models'; +import { LivechatContacts, LivechatDepartment, LivechatDepartmentAgents, LivechatInquiry, LivechatRooms, Users } from '@rocket.chat/models'; import { Random } from '@rocket.chat/random'; import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; @@ -25,7 +25,7 @@ import { } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; import { i18n } from '../../../utils/lib/i18n'; -import { shouldTriggerVerificationApp, unverifyContactChannel } from './Contacts'; +import { shouldTriggerVerificationApp } from './Contacts'; import { createLivechatRoom, createLivechatInquiry, allowAgentSkipQueue } from './Helper'; import { Livechat } from './LivechatTyped'; import { RoutingManager } from './RoutingManager'; @@ -262,7 +262,7 @@ export class QueueManager { // up. So if the contact is not verified we don't add it to queue and wait an event to be sent to properly add it to queue if (inquiry.v.contactId && (await shouldTriggerVerificationApp(inquiry.v.contactId, room.source))) { newRoom = await LivechatRooms.findOneById(rid); - await unverifyContactChannel(inquiry.v.contactId, room.source.type, inquiry.v._id); + await LivechatContacts.unverifyContactChannel(inquiry.v.contactId, room.source.type, inquiry.v._id); // void Apps.self?.triggerEvent(AppEvents.IPostContactVerificationAppAssigned, inquiry.v, room); } else { newRoom = (await this.queueInquiry(inquiry, room, defaultAgent)) ?? (await LivechatRooms.findOneById(rid)); diff --git a/apps/meteor/server/models/raw/LivechatContacts.ts b/apps/meteor/server/models/raw/LivechatContacts.ts index 04d5e03e686f..27edb041465a 100644 --- a/apps/meteor/server/models/raw/LivechatContacts.ts +++ b/apps/meteor/server/models/raw/LivechatContacts.ts @@ -172,4 +172,11 @@ export class LivechatContactsRaw extends BaseRaw implements IL options, ).toArray(); } + + async unverifyContactChannel(contactId: ILivechatContact['_id'], channelName: string, visitorId: string): Promise { + return this.updateOne( + { '_id': contactId, 'channels.visitorId': visitorId, 'channels.channelName': channelName }, + { $set: { 'channels.$.verified': false } }, + ); + } } diff --git a/packages/model-typings/src/models/ILivechatContactsModel.ts b/packages/model-typings/src/models/ILivechatContactsModel.ts index 5c291745e2e4..fcd67ee80a64 100644 --- a/packages/model-typings/src/models/ILivechatContactsModel.ts +++ b/packages/model-typings/src/models/ILivechatContactsModel.ts @@ -13,6 +13,7 @@ export interface ILivechatContactsModel extends IBaseModel { addChannel(contactId: string, channel: ILivechatContactChannel): Promise; findPaginatedContacts(searchText?: string, options?: FindOptions): FindPaginated>; updateLastChatById(contactId: string, visitorId: string, lastChat: ILivechatContact['lastChat']): Promise; + unverifyContactChannel(contactId: ILivechatContact['_id'], channelName: string, visitorId: string): Promise; findContactMatchingVisitor(visitor: AtLeast): Promise; findOneByVisitorId( visitorId: ILivechatVisitor['_id'], From 696e3e30f45534463e079bdc517dc597d5d6e3bf Mon Sep 17 00:00:00 2001 From: gustrb Date: Thu, 17 Oct 2024 14:17:03 -0300 Subject: [PATCH 31/37] chore: remove isSingleContactId call --- apps/meteor/app/livechat/server/lib/Helper.ts | 2 +- .../meteor/app/livechat/server/methods/takeInquiry.ts | 11 ++--------- .../ee/server/patches/shouldTriggerVerificationApp.ts | 11 ++++++++++- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index 16cd9cf0c79a..a01c848b1ae1 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -45,7 +45,7 @@ import { notifyOnSubscriptionChanged, } from '../../../lib/server/lib/notifyListener'; import { settings } from '../../../settings/server'; -import { migrateVisitorIfMissingContact } from './Contacts'; +import { getContactIdByVisitorId, migrateVisitorIfMissingContact } from './Contacts'; import { Livechat as LivechatTyped } from './LivechatTyped'; import { queueInquiry, saveQueueInquiry } from './QueueManager'; import { RoutingManager } from './RoutingManager'; diff --git a/apps/meteor/app/livechat/server/methods/takeInquiry.ts b/apps/meteor/app/livechat/server/methods/takeInquiry.ts index 86c24e71ac20..98dab4e1ed6b 100644 --- a/apps/meteor/app/livechat/server/methods/takeInquiry.ts +++ b/apps/meteor/app/livechat/server/methods/takeInquiry.ts @@ -5,7 +5,7 @@ import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { settings } from '../../../settings/server'; -import { isSingleContactEnabled, shouldTriggerVerificationApp, migrateVisitorIfMissingContact } from '../lib/Contacts'; +import { shouldTriggerVerificationApp, migrateVisitorIfMissingContact } from '../lib/Contacts'; import { RoutingManager } from '../lib/RoutingManager'; declare module '@rocket.chat/ddp-client' { @@ -55,14 +55,7 @@ export const takeInquiry = async ( throw new Error('error-mac-limit-reached'); } - const contactId = await (async () => { - if (!isSingleContactEnabled()) { - return undefined; - } - - return migrateVisitorIfMissingContact(inquiry.v._id, room.source); - })(); - + const contactId = await migrateVisitorIfMissingContact(inquiry.v._id, room.source); if (contactId && (await shouldTriggerVerificationApp(contactId, room.source))) { throw new Error('error-unverified-contact'); } diff --git a/apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts b/apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts index 2c46388d5e4f..248bf764cb90 100644 --- a/apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts +++ b/apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts @@ -1,11 +1,16 @@ import type { IOmnichannelSource, ILivechatContact } from '@rocket.chat/core-typings'; +import { License } from '@rocket.chat/license'; import { LivechatContacts } from '@rocket.chat/models'; import { hasLicense } from '../../../app/license/client'; import { shouldTriggerVerificationApp } from '../../../app/livechat/server/lib/Contacts'; import { settings } from '../../../app/settings/server'; -shouldTriggerVerificationApp.patch(async (_next, contactId: ILivechatContact['_id'], source: IOmnichannelSource): Promise => { +const runShouldTriggerVerificationApp = async ( + _next: any, + contactId: ILivechatContact['_id'], + source: IOmnichannelSource, +): Promise => { const hasContactIdLicense = await hasLicense('contact-id-verification'); if (hasContactIdLicense) { @@ -40,4 +45,8 @@ shouldTriggerVerificationApp.patch(async (_next, contactId: ILivechatContact['_i } return false; +}; + +void License.onLicense('contact-id-verification', () => { + shouldTriggerVerificationApp.patch(runShouldTriggerVerificationApp); }); From bba7f8a281d88da516254bee5640ef04109b9e31 Mon Sep 17 00:00:00 2001 From: gustrb Date: Thu, 17 Oct 2024 14:23:41 -0300 Subject: [PATCH 32/37] chore: remove server import of client field --- .../ee/server/patches/shouldTriggerVerificationApp.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts b/apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts index 248bf764cb90..8cf4ac38f392 100644 --- a/apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts +++ b/apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts @@ -2,7 +2,6 @@ import type { IOmnichannelSource, ILivechatContact } from '@rocket.chat/core-typ import { License } from '@rocket.chat/license'; import { LivechatContacts } from '@rocket.chat/models'; -import { hasLicense } from '../../../app/license/client'; import { shouldTriggerVerificationApp } from '../../../app/livechat/server/lib/Contacts'; import { settings } from '../../../app/settings/server'; @@ -11,12 +10,6 @@ const runShouldTriggerVerificationApp = async ( contactId: ILivechatContact['_id'], source: IOmnichannelSource, ): Promise => { - const hasContactIdLicense = await hasLicense('contact-id-verification'); - - if (hasContactIdLicense) { - return false; - } - const contact = await LivechatContacts.findOneById>(contactId, { projection: { _id: 1, From 045471b97ab809fe1f779ae3f5e9e62a4cb92b91 Mon Sep 17 00:00:00 2001 From: gustrb Date: Thu, 17 Oct 2024 14:26:59 -0300 Subject: [PATCH 33/37] chore: use License.hasModule --- apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts b/apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts index 8cf4ac38f392..b46093bdf884 100644 --- a/apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts +++ b/apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts @@ -40,6 +40,4 @@ const runShouldTriggerVerificationApp = async ( return false; }; -void License.onLicense('contact-id-verification', () => { - shouldTriggerVerificationApp.patch(runShouldTriggerVerificationApp); -}); +shouldTriggerVerificationApp.patch(runShouldTriggerVerificationApp, License.hasModule('contact-id-verification')); From 1e31245842470063e09390abcfd1fca2952e8e24 Mon Sep 17 00:00:00 2001 From: gustrb Date: Thu, 17 Oct 2024 14:27:38 -0300 Subject: [PATCH 34/37] chore: improve shouldTriggerVerificationApp --- apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts b/apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts index b46093bdf884..f5296c7faf02 100644 --- a/apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts +++ b/apps/meteor/ee/server/patches/shouldTriggerVerificationApp.ts @@ -40,4 +40,4 @@ const runShouldTriggerVerificationApp = async ( return false; }; -shouldTriggerVerificationApp.patch(runShouldTriggerVerificationApp, License.hasModule('contact-id-verification')); +shouldTriggerVerificationApp.patch(runShouldTriggerVerificationApp, () => License.hasModule('contact-id-verification')); From b25ac47ad28856936ddd7ddb4db1992929cfa0f6 Mon Sep 17 00:00:00 2001 From: gustrb Date: Fri, 18 Oct 2024 01:09:23 -0300 Subject: [PATCH 35/37] chore: remove unused method --- apps/meteor/server/models/raw/LivechatInquiry.ts | 9 --------- .../model-typings/src/models/ILivechatInquiryModel.ts | 1 - 2 files changed, 10 deletions(-) diff --git a/apps/meteor/server/models/raw/LivechatInquiry.ts b/apps/meteor/server/models/raw/LivechatInquiry.ts index 1f85be69ed05..6746d6f87926 100644 --- a/apps/meteor/server/models/raw/LivechatInquiry.ts +++ b/apps/meteor/server/models/raw/LivechatInquiry.ts @@ -411,15 +411,6 @@ export class LivechatInquiryRaw extends BaseRaw implemen return this.findOne(query); } - findOneReadyByContactId(contactId: string): Promise { - const query: Filter = { - 'v.contactId': contactId, - 'status': LivechatInquiryStatus.READY, - }; - - return this.findOne(query); - } - removeDefaultAgentById(inquiryId: string): Promise { return this.updateOne( { diff --git a/packages/model-typings/src/models/ILivechatInquiryModel.ts b/packages/model-typings/src/models/ILivechatInquiryModel.ts index 2a2c368d2d0a..3535b65c3518 100644 --- a/packages/model-typings/src/models/ILivechatInquiryModel.ts +++ b/packages/model-typings/src/models/ILivechatInquiryModel.ts @@ -43,5 +43,4 @@ export interface ILivechatInquiryModel extends IBaseModel; markInquiryActiveForPeriod(rid: ILivechatInquiryRecord['rid'], period: string): Promise; findIdsByVisitorToken(token: ILivechatInquiryRecord['v']['token']): FindCursor; - findOneReadyByContactId(contactId: string): Promise; } From cf869aa054d978597e5024d1f32ef7da10612283 Mon Sep 17 00:00:00 2001 From: gustrb Date: Fri, 18 Oct 2024 01:31:02 -0300 Subject: [PATCH 36/37] chore: remove uncessary method --- apps/meteor/app/livechat/server/lib/QueueManager.ts | 2 +- apps/meteor/server/models/raw/LivechatContacts.ts | 7 ------- .../model-typings/src/models/ILivechatContactsModel.ts | 1 - 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/apps/meteor/app/livechat/server/lib/QueueManager.ts b/apps/meteor/app/livechat/server/lib/QueueManager.ts index 179b84da1a23..de569e4cec10 100644 --- a/apps/meteor/app/livechat/server/lib/QueueManager.ts +++ b/apps/meteor/app/livechat/server/lib/QueueManager.ts @@ -262,7 +262,7 @@ export class QueueManager { // up. So if the contact is not verified we don't add it to queue and wait an event to be sent to properly add it to queue if (inquiry.v.contactId && (await shouldTriggerVerificationApp(inquiry.v.contactId, room.source))) { newRoom = await LivechatRooms.findOneById(rid); - await LivechatContacts.unverifyContactChannel(inquiry.v.contactId, room.source.type, inquiry.v._id); + await LivechatContacts.updateContactChannel(inquiry.v._id, { verified: true }); // void Apps.self?.triggerEvent(AppEvents.IPostContactVerificationAppAssigned, inquiry.v, room); } else { newRoom = (await this.queueInquiry(inquiry, room, defaultAgent)) ?? (await LivechatRooms.findOneById(rid)); diff --git a/apps/meteor/server/models/raw/LivechatContacts.ts b/apps/meteor/server/models/raw/LivechatContacts.ts index 96fb7b5bea6e..0c9a4f3d6341 100644 --- a/apps/meteor/server/models/raw/LivechatContacts.ts +++ b/apps/meteor/server/models/raw/LivechatContacts.ts @@ -195,11 +195,4 @@ export class LivechatContactsRaw extends BaseRaw implements IL options, ).toArray(); } - - async unverifyContactChannel(contactId: ILivechatContact['_id'], channelName: string, visitorId: string): Promise { - return this.updateOne( - { '_id': contactId, 'channels.visitorId': visitorId, 'channels.channelName': channelName }, - { $set: { 'channels.$.verified': false } }, - ); - } } diff --git a/packages/model-typings/src/models/ILivechatContactsModel.ts b/packages/model-typings/src/models/ILivechatContactsModel.ts index d2e047da0e9e..aa0bacdda1d4 100644 --- a/packages/model-typings/src/models/ILivechatContactsModel.ts +++ b/packages/model-typings/src/models/ILivechatContactsModel.ts @@ -12,7 +12,6 @@ export interface ILivechatContactsModel extends IBaseModel { addChannel(contactId: string, channel: ILivechatContactChannel): Promise; findPaginatedContacts(searchText?: string, options?: FindOptions): FindPaginated>; updateLastChatById(contactId: string, visitorId: string, lastChat: ILivechatContact['lastChat']): Promise; - unverifyContactChannel(contactId: ILivechatContact['_id'], channelName: string, visitorId: string): Promise; findContactMatchingVisitor(visitor: AtLeast): Promise; findOneByVisitorId( visitorId: ILivechatVisitor['_id'], From 569f9027a1d69bcd994deaa73e01e41b4c1f3ebd Mon Sep 17 00:00:00 2001 From: gustrb Date: Fri, 18 Oct 2024 01:32:26 -0300 Subject: [PATCH 37/37] chore: remove changeset --- .changeset/clean-ravens-melt.md | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 .changeset/clean-ravens-melt.md diff --git a/.changeset/clean-ravens-melt.md b/.changeset/clean-ravens-melt.md deleted file mode 100644 index 4caa49236fe8..000000000000 --- a/.changeset/clean-ravens-melt.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@rocket.chat/meteor": minor -"@rocket.chat/core-typings": minor ---- - -Added four new settings to control how the agent might be handled down to the Omnichannel queue, they are: -- Livechat_Block_Unknown_Contacts: if the contact associated with the livechat visitor is unknown, the conversation won't be handled down to an agent; -- Livechat_Block_Unverified_Contacts: if the contact has no verified channel, the conversation won't be handled down to an agent; -- Livechat_Request_Verification_On_First_Contact_Only: if the contact associated with the livechat visitor should be verified every time he starts a new conversation or only once; -- Livechat_Contact_Verification_App: Defines the app that is going to be used to verify the contact. \ No newline at end of file