From 97749576646fa027623996502a43f54cb6ed8323 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 14 Oct 2024 15:52:44 -0300 Subject: [PATCH 01/23] feat: add channelName property to ILivechatVisitor --- packages/core-typings/src/ILivechatVisitor.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core-typings/src/ILivechatVisitor.ts b/packages/core-typings/src/ILivechatVisitor.ts index eefb4ebd720c8..580abcd820ecb 100644 --- a/packages/core-typings/src/ILivechatVisitor.ts +++ b/packages/core-typings/src/ILivechatVisitor.ts @@ -50,6 +50,7 @@ export interface ILivechatVisitor extends IRocketChatRecord { activity?: string[]; disabled?: boolean; contactId?: string; + channelName: string; } export interface ILivechatVisitorDTO { From 60609451d6d7dc6b5042341f77ed3d61406d04e7 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 14 Oct 2024 15:57:41 -0300 Subject: [PATCH 02/23] feat: check only for visitors within the app context in the livechat apps bridge --- .../app/apps/server/bridges/livechat.ts | 17 ++++-- .../app/apps/server/converters/visitors.js | 12 ++++- .../server/models/raw/LivechatVisitors.ts | 54 +++++++++++++++++-- .../src/converters/IAppVisitorsConverter.ts | 3 +- .../src/models/ILivechatVisitorsModel.ts | 20 +++++-- 5 files changed, 92 insertions(+), 14 deletions(-) diff --git a/apps/meteor/app/apps/server/bridges/livechat.ts b/apps/meteor/app/apps/server/bridges/livechat.ts index 821d1fdd60d53..bfc0ade99887c 100644 --- a/apps/meteor/app/apps/server/bridges/livechat.ts +++ b/apps/meteor/app/apps/server/bridges/livechat.ts @@ -52,6 +52,13 @@ export class AppLivechatBridge extends LivechatBridge { const appMessage = (await this.orch.getConverters().get('messages').convertAppMessage(message)) as IMessage | undefined; const livechatMessage = appMessage as ILivechatMessage | undefined; + if (guest) { + const fullVisitor = await LivechatVisitors.findOneEnabledByIdAndChannelName({ _id: guest._id, channelName: appId }); + if (!fullVisitor?.channelName) { + await LivechatVisitors.setChannelNameById(guest._id, appId); + } + } + const msg = await LivechatTyped.sendMessage({ guest: guest as ILivechatVisitor, message: livechatMessage as ILivechatMessage, @@ -286,7 +293,7 @@ export class AppLivechatBridge extends LivechatBridge { } return Promise.all( - (await LivechatVisitors.findEnabled(query).toArray()).map( + (await LivechatVisitors.findEnabledByChannelName(appId, query).toArray()).map( async (visitor) => visitor && this.orch.getConverters()?.get('visitors').convertVisitor(visitor), ), ); @@ -295,7 +302,7 @@ export class AppLivechatBridge extends LivechatBridge { protected async findVisitorById(id: string, appId: string): Promise { this.orch.debugLog(`The App ${appId} is looking for livechat visitors.`); - return this.orch.getConverters()?.get('visitors').convertById(id); + return this.orch.getConverters()?.get('visitors').convertById(id, appId); } protected async findVisitorByEmail(email: string, appId: string): Promise { @@ -304,7 +311,7 @@ export class AppLivechatBridge extends LivechatBridge { return this.orch .getConverters() ?.get('visitors') - .convertVisitor(await LivechatVisitors.findOneGuestByEmailAddress(email)); + .convertVisitor(await LivechatVisitors.findOneGuestByEmailAddress(email, appId)); } protected async findVisitorByToken(token: string, appId: string): Promise { @@ -313,7 +320,7 @@ export class AppLivechatBridge extends LivechatBridge { return this.orch .getConverters() ?.get('visitors') - .convertVisitor(await LivechatVisitors.getVisitorByToken(token, {})); + .convertVisitor(await LivechatVisitors.getVisitorByTokenAndChannelName({ token, channelName: appId })); } protected async findVisitorByPhoneNumber(phoneNumber: string, appId: string): Promise { @@ -322,7 +329,7 @@ export class AppLivechatBridge extends LivechatBridge { return this.orch .getConverters() ?.get('visitors') - .convertVisitor(await LivechatVisitors.findOneVisitorByPhone(phoneNumber)); + .convertVisitor(await LivechatVisitors.findOneVisitorByPhone(phoneNumber, appId)); } protected async findDepartmentByIdOrName(value: string, appId: string): Promise { diff --git a/apps/meteor/app/apps/server/converters/visitors.js b/apps/meteor/app/apps/server/converters/visitors.js index 32864e3e900e8..7d01b010ac6ec 100644 --- a/apps/meteor/app/apps/server/converters/visitors.js +++ b/apps/meteor/app/apps/server/converters/visitors.js @@ -8,8 +8,8 @@ export class AppVisitorsConverter { this.orch = orch; } - async convertById(id) { - const visitor = await LivechatVisitors.findOneEnabledById(id); + async convertById(id, appId) { + const visitor = await LivechatVisitors.findOneEnabledByIdAndChannelName({ _id: id, channelName: appId }); return this.convertVisitor(visitor); } @@ -20,6 +20,12 @@ export class AppVisitorsConverter { return this.convertVisitor(visitor); } + async convertByTokenAndChannelName(token, appId) { + const visitor = await LivechatVisitors.getVisitorByTokenAndChannelName({ token, channelName: appId }); + + return this.convertVisitor(visitor); + } + async convertVisitor(visitor) { if (!visitor) { return undefined; @@ -37,6 +43,7 @@ export class AppVisitorsConverter { livechatData: 'livechatData', status: 'status', contactId: 'contactId', + channelName: 'channelName', }; return transformMappedData(visitor, map); @@ -56,6 +63,7 @@ export class AppVisitorsConverter { livechatData: visitor.livechatData, status: visitor.status || 'online', contactId: visitor.contactId, + channelName: visitor.channelName, ...(visitor.visitorEmails && { visitorEmails: visitor.visitorEmails }), ...(visitor.department && { department: visitor.department }), }; diff --git a/apps/meteor/server/models/raw/LivechatVisitors.ts b/apps/meteor/server/models/raw/LivechatVisitors.ts index 396b728159ff6..f84593bb60944 100644 --- a/apps/meteor/server/models/raw/LivechatVisitors.ts +++ b/apps/meteor/server/models/raw/LivechatVisitors.ts @@ -30,6 +30,7 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL protected modelIndexes(): IndexDescription[] { return [ { key: { token: 1 } }, + { key: { token: 1, channelName: 1 } }, { key: { 'phone.phoneNumber': 1 }, sparse: true }, { key: { 'visitorEmails.address': 1 }, sparse: true }, { key: { name: 1 }, sparse: true }, @@ -41,17 +42,21 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL ]; } - findOneVisitorByPhone(phone: string): Promise { + findOneVisitorByPhone(phone: string, channelName?: string): Promise { + const emptyChannelNameFilter = { channelName: { $exists: false } }; const query = { 'phone.phoneNumber': phone, + ...(channelName ? { $or: [{ channelName }, emptyChannelNameFilter] } : emptyChannelNameFilter), }; return this.findOne(query); } - findOneGuestByEmailAddress(emailAddress: string): Promise { + findOneGuestByEmailAddress(emailAddress: string, channelName?: string): Promise { + const emptyChannelNameFilter = { channelName: { $exists: false } }; const query = { 'visitorEmails.address': String(emailAddress).toLowerCase(), + ...(channelName ? { $or: [{ channelName }, emptyChannelNameFilter] } : emptyChannelNameFilter), }; return this.findOne(query); @@ -69,10 +74,15 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL return this.find(query, options); } - findEnabled(query: Filter, options?: FindOptions): FindCursor { + findEnabledByChannelName( + channelName: string, + query: Filter, + options?: FindOptions, + ): FindCursor { return this.find( { ...query, + $or: [{ channelName }, { channelName: { $exists: false } }], disabled: { $ne: true }, }, options, @@ -88,6 +98,20 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL return this.findOne(query, options); } + findOneEnabledByIdAndChannelName( + { _id, channelName }: { _id: string; channelName: string }, + options?: FindOptions, + ): Promise { + const emptyChannelNameFilter = { channelName: { $exists: false } }; + const query = { + _id, + disabled: { $ne: true }, + ...(channelName ? { $or: [{ channelName }, emptyChannelNameFilter] } : emptyChannelNameFilter), + }; + + return this.findOne(query, options); + } + findVisitorByToken(token: string): FindCursor { const query = { token, @@ -105,6 +129,19 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL return this.findOne(query, options); } + getVisitorByTokenAndChannelName( + { token, channelName }: { token: string; channelName?: string }, + options: FindOptions, + ): Promise { + const emptyChannelNameFilter = { channelName: { $exists: false } }; + const query = { + token, + ...(channelName ? { $or: [{ channelName }, emptyChannelNameFilter] } : emptyChannelNameFilter), + }; + + return this.findOne(query, options); + } + getVisitorsBetweenDate({ start, end, department }: { start: Date; end: Date; department?: string }): FindCursor { const query = { disabled: { $ne: true }, @@ -470,6 +507,17 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL }, ); } + + setChannelNameById(_id: string, channelName: Required): Promise { + return this.updateOne( + { _id }, + { + $set: { + channelName, + }, + }, + ); + } } type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable }; diff --git a/packages/apps/src/converters/IAppVisitorsConverter.ts b/packages/apps/src/converters/IAppVisitorsConverter.ts index 575845b57c105..fe9fa8eb10da9 100644 --- a/packages/apps/src/converters/IAppVisitorsConverter.ts +++ b/packages/apps/src/converters/IAppVisitorsConverter.ts @@ -3,8 +3,9 @@ import type { ILivechatVisitor } from '@rocket.chat/core-typings'; import type { IAppsVisitor } from '../AppsEngine'; export interface IAppVisitorsConverter { - convertById(visitorId: ILivechatVisitor['_id']): Promise; + convertById(visitorId: ILivechatVisitor['_id'], appId?: string): Promise; convertByToken(token: string): Promise; + convertByTokenAndChannelName(token: string, appId?: string): Promise; convertVisitor(visitor: undefined | null): Promise; convertVisitor(visitor: ILivechatVisitor): Promise; convertVisitor(visitor: ILivechatVisitor | undefined | null): Promise; diff --git a/packages/model-typings/src/models/ILivechatVisitorsModel.ts b/packages/model-typings/src/models/ILivechatVisitorsModel.ts index 3e17fc2a59624..73f05b299e388 100644 --- a/packages/model-typings/src/models/ILivechatVisitorsModel.ts +++ b/packages/model-typings/src/models/ILivechatVisitorsModel.ts @@ -16,6 +16,10 @@ import type { FindPaginated, IBaseModel } from './IBaseModel'; export interface ILivechatVisitorsModel extends IBaseModel { findById(_id: string, options?: FindOptions): FindCursor; getVisitorByToken(token: string, options?: FindOptions): Promise; + getVisitorByTokenAndChannelName( + { token, channelName }: { token: string; channelName?: string }, + options?: FindOptions, + ): Promise; getVisitorsBetweenDate({ start, end, department }: { start: Date; end: Date; department?: string }): FindCursor; findByNameRegexWithExceptionsAndConditions

( searchTerm: string, @@ -45,9 +49,9 @@ export interface ILivechatVisitorsModel extends IBaseModel { updateLivechatDataByToken(token: string, key: string, value: unknown, overwrite: boolean): Promise; - findOneGuestByEmailAddress(emailAddress: string): Promise; + findOneGuestByEmailAddress(emailAddress: string, channelName?: string): Promise; - findOneVisitorByPhone(phone: string): Promise; + findOneVisitorByPhone(phone: string, channelName?: string): Promise; removeDepartmentById(_id: string): Promise; @@ -67,9 +71,18 @@ export interface ILivechatVisitorsModel extends IBaseModel { findOneEnabledById(_id: string, options?: FindOptions): Promise; + findOneEnabledByIdAndChannelName( + { _id, channelName }: { _id: string; channelName: string }, + options?: FindOptions, + ): Promise; + disableById(_id: string): Promise; - findEnabled(query: Filter, options?: FindOptions): FindCursor; + findEnabledByChannelName( + channelName: string, + query: Filter, + options?: FindOptions, + ): FindCursor; countVisitorsOnPeriod(period: string): Promise; saveGuestById( @@ -77,4 +90,5 @@ export interface ILivechatVisitorsModel extends IBaseModel { data: { name?: string; username?: string; email?: string; phone?: string; livechatData: { [k: string]: any } }, ): Promise; setLastChatById(_id: string, lastChat: Required): Promise; + setChannelNameById(_id: string, channelName: Required): Promise; } From 4bd9dbf3537bba932315ff7f7f64189b3e7b7e02 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 14 Oct 2024 16:00:12 -0300 Subject: [PATCH 03/23] feat: restrict visitors by channelName in the SMS messages flow --- apps/meteor/app/livechat/imports/server/rest/sms.ts | 5 +++-- apps/meteor/app/livechat/server/lib/LivechatTyped.ts | 5 ++++- apps/meteor/app/livechat/server/sendMessageBySMS.ts | 11 +++++++++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/apps/meteor/app/livechat/imports/server/rest/sms.ts b/apps/meteor/app/livechat/imports/server/rest/sms.ts index 15f08cdc1e83b..6c820e958c033 100644 --- a/apps/meteor/app/livechat/imports/server/rest/sms.ts +++ b/apps/meteor/app/livechat/imports/server/rest/sms.ts @@ -56,9 +56,10 @@ const defineDepartment = async (idOrName?: string) => { }; const defineVisitor = async (smsNumber: string, targetDepartment?: string) => { - const visitor = await LivechatVisitors.findOneVisitorByPhone(smsNumber); - let data: { token: string; department?: string } = { + const visitor = await LivechatVisitors.findOneVisitorByPhone(smsNumber, OmnichannelSourceType.SMS); + let data: { token: string; channelName: string; department?: string } = { token: visitor?.token || Random.id(), + channelName: OmnichannelSourceType.SMS, }; if (!visitor) { diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index e521ac98fe711..763bc8e23503e 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -81,7 +81,7 @@ import { isDepartmentCreationAvailable } from './isDepartmentCreationAvailable'; import type { CloseRoomParams, CloseRoomParamsByUser, CloseRoomParamsByVisitor } from './localTypes'; import { parseTranscriptRequest } from './parseTranscriptRequest'; -type RegisterGuestType = Partial> & { +type RegisterGuestType = Partial> & { id?: string; connectionData?: any; email?: string; @@ -654,6 +654,7 @@ class LivechatClass { username, connectionData, status = UserStatus.ONLINE, + channelName, }: RegisterGuestType): Promise { check(token, String); check(id, Match.Maybe(String)); @@ -663,6 +664,7 @@ class LivechatClass { const visitorDataToUpdate: Partial & { userAgent?: string; ip?: string; host?: string } = { token, status, + channelName, ...(phone?.number ? { phone: [{ phoneNumber: phone.number }] } : {}), ...(name ? { name } : {}), }; @@ -708,6 +710,7 @@ class LivechatClass { visitorDataToUpdate.username = username || (await LivechatVisitors.getNextVisitorUsername()); visitorDataToUpdate.status = status; visitorDataToUpdate.ts = new Date(); + visitorDataToUpdate.channelName = channelName; if (settings.get('Livechat_Allow_collect_and_store_HTTP_header_informations') && Livechat.isValidObject(connectionData)) { Livechat.logger.debug(`Saving connection data for visitor ${token}`); diff --git a/apps/meteor/app/livechat/server/sendMessageBySMS.ts b/apps/meteor/app/livechat/server/sendMessageBySMS.ts index c7f88646158b9..f60db26862b17 100644 --- a/apps/meteor/app/livechat/server/sendMessageBySMS.ts +++ b/apps/meteor/app/livechat/server/sendMessageBySMS.ts @@ -1,5 +1,5 @@ import { OmnichannelIntegration } from '@rocket.chat/core-services'; -import { isEditedMessage } from '@rocket.chat/core-typings'; +import { isEditedMessage, OmnichannelSourceType } from '@rocket.chat/core-typings'; import { LivechatVisitors } from '@rocket.chat/models'; import { callbacks } from '../../../lib/callbacks'; @@ -55,12 +55,19 @@ callbacks.add( return message; } - const visitor = await LivechatVisitors.getVisitorByToken(room.v.token, { projection: { phone: 1 } }); + const visitor = await LivechatVisitors.getVisitorByTokenAndChannelName( + { token: room.v.token, channelName: OmnichannelSourceType.SMS }, + { projection: { phone: 1, channelName: 1 } }, + ); if (!visitor?.phone || visitor.phone.length === 0) { return message; } + if (!visitor.channelName) { + await LivechatVisitors.setChannelNameById(visitor._id, OmnichannelSourceType.SMS); + } + try { await SMSService.send(room.sms.from, visitor.phone[0].phoneNumber, message.msg, extraData); callbackLogger.debug(`SMS message sent to ${visitor.phone[0].phoneNumber} via ${service}`); From 39e95765e53f1f59d94d5aeb5afb28878b9a70f6 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 14 Oct 2024 16:00:49 -0300 Subject: [PATCH 04/23] feat: restrict visitors by channelName in the Email inbox messages flow --- .../server/features/EmailInbox/EmailInbox_Incoming.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts index 7ecb8f309b29d..1b549d103e261 100644 --- a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts +++ b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts @@ -25,7 +25,7 @@ const language = settings.get('Language') || 'en'; const t = i18n.getFixedT(language); async function getGuestByEmail(email: string, name: string, department = ''): Promise { - const guest = await LivechatVisitors.findOneGuestByEmailAddress(email); + const guest = await LivechatVisitors.findOneGuestByEmailAddress(email, OmnichannelSourceType.EMAIL); if (guest) { if (guest.department !== department) { @@ -37,6 +37,9 @@ async function getGuestByEmail(email: string, name: string, department = ''): Pr await LivechatTyped.setDepartmentForGuest({ token: guest.token, department }); return LivechatVisitors.findOneEnabledById(guest._id, {}); } + if (!guest.channelName) { + await LivechatVisitors.setChannelNameById(guest._id, OmnichannelSourceType.EMAIL); + } return guest; } @@ -45,6 +48,7 @@ async function getGuestByEmail(email: string, name: string, department = ''): Pr name: name || email, email, department, + channelName: OmnichannelSourceType.EMAIL, }); if (!livechatVisitor) { From 7c621d39c07689005199366d732297c5190f50c0 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 14 Oct 2024 16:01:19 -0300 Subject: [PATCH 05/23] feat: restrict visitors by channelName in the API and Widget messages flows --- .../livechat/imports/server/rest/upload.ts | 9 +++++- .../app/livechat/server/api/lib/livechat.ts | 28 +++++++++++-------- .../app/livechat/server/api/v1/message.ts | 21 ++++++++++---- .../server/methods/sendMessageLivechat.ts | 21 +++++++++----- 4 files changed, 54 insertions(+), 25 deletions(-) diff --git a/apps/meteor/app/livechat/imports/server/rest/upload.ts b/apps/meteor/app/livechat/imports/server/rest/upload.ts index 14db8f20afcf2..058dd6389dce3 100644 --- a/apps/meteor/app/livechat/imports/server/rest/upload.ts +++ b/apps/meteor/app/livechat/imports/server/rest/upload.ts @@ -1,7 +1,9 @@ +import { OmnichannelSourceType } from '@rocket.chat/core-typings'; import { LivechatVisitors, LivechatRooms } from '@rocket.chat/models'; import filesize from 'filesize'; import { API } from '../../../../api/server'; +import { isWidget } from '../../../../api/server/helpers/isWidget'; import { getUploadFormData } from '../../../../api/server/lib/getUploadFormData'; import { FileUpload } from '../../../../file-upload/server'; import { settings } from '../../../../settings/server'; @@ -13,6 +15,7 @@ API.v1.addRoute('livechat/upload/:rid', { if (!this.request.headers['x-visitor-token']) { return API.v1.unauthorized(); } + const channelName = isWidget(this.request.headers) ? OmnichannelSourceType.WIDGET : OmnichannelSourceType.API; const canUpload = settings.get('Livechat_fileupload_enabled') && settings.get('FileUpload_Enabled'); @@ -23,7 +26,7 @@ API.v1.addRoute('livechat/upload/:rid', { } const visitorToken = this.request.headers['x-visitor-token']; - const visitor = await LivechatVisitors.getVisitorByToken(visitorToken as string, {}); + const visitor = await LivechatVisitors.getVisitorByTokenAndChannelName({ token: visitorToken as string, channelName }); if (!visitor) { return API.v1.unauthorized(); @@ -76,6 +79,10 @@ API.v1.addRoute('livechat/upload/:rid', { return API.v1.failure('Invalid file'); } + if (!visitor.channelName) { + await LivechatVisitors.setChannelNameById(visitor._id, channelName); + } + uploadedFile.description = fields.description; delete fields.description; diff --git a/apps/meteor/app/livechat/server/api/lib/livechat.ts b/apps/meteor/app/livechat/server/api/lib/livechat.ts index 01c4d9736c66a..bb36d263a7d2e 100644 --- a/apps/meteor/app/livechat/server/api/lib/livechat.ts +++ b/apps/meteor/app/livechat/server/api/lib/livechat.ts @@ -48,18 +48,22 @@ async function findDepartments( })); } -export function findGuest(token: string): Promise { - return LivechatVisitors.getVisitorByToken(token, { - projection: { - name: 1, - username: 1, - token: 1, - visitorEmails: 1, - department: 1, - activity: 1, - contactId: 1, - }, - }); +export function findGuest(token: string, channelName?: string): Promise { + const projection = { + name: 1, + username: 1, + token: 1, + visitorEmails: 1, + department: 1, + activity: 1, + contactId: 1, + channelName: 1, + }; + + if (channelName) { + return LivechatVisitors.getVisitorByTokenAndChannelName({ token, channelName }, { projection }); + } + return LivechatVisitors.getVisitorByToken(token, { projection }); } export function findGuestWithoutActivity(token: string): Promise { diff --git a/apps/meteor/app/livechat/server/api/v1/message.ts b/apps/meteor/app/livechat/server/api/v1/message.ts index b7eb6e1f684a0..dffbf2e2f04e7 100644 --- a/apps/meteor/app/livechat/server/api/v1/message.ts +++ b/apps/meteor/app/livechat/server/api/v1/message.ts @@ -26,8 +26,9 @@ API.v1.addRoute( { async post() { const { token, rid, agent, msg } = this.bodyParams; + const channelName = isWidget(this.request.headers) ? OmnichannelSourceType.WIDGET : OmnichannelSourceType.API; - const guest = await findGuest(token); + const guest = await findGuest(token, channelName); if (!guest) { throw new Error('invalid-token'); } @@ -48,6 +49,10 @@ API.v1.addRoute( throw new Error('message-length-exceeds-character-limit'); } + if (!guest.channelName) { + await LivechatVisitors.setChannelNameById(guest._id, channelName); + } + const _id = this.bodyParams._id || Random.id(); const sendMessage = { @@ -61,7 +66,7 @@ API.v1.addRoute( agent, roomInfo: { source: { - type: isWidget(this.request.headers) ? OmnichannelSourceType.WIDGET : OmnichannelSourceType.API, + type: channelName, }, }, }; @@ -250,8 +255,9 @@ API.v1.addRoute( { async post() { const visitorToken = this.bodyParams.visitor.token; + const channelName = isWidget(this.request.headers) ? OmnichannelSourceType.WIDGET : OmnichannelSourceType.API; - const visitor = await LivechatVisitors.getVisitorByToken(visitorToken, {}); + const visitor = await LivechatVisitors.getVisitorByTokenAndChannelName({ token: visitorToken, channelName }, {}); let rid: string; if (visitor) { const extraQuery = await callbacks.run('livechat.applyRoomRestrictions', {}); @@ -261,11 +267,16 @@ API.v1.addRoute( } else { rid = Random.id(); } + + if (!visitor.channelName) { + await LivechatVisitors.setChannelNameById(visitor._id, channelName); + } } else { rid = Random.id(); - const guest: typeof this.bodyParams.visitor & { connectionData?: unknown } = this.bodyParams.visitor; + const guest: typeof this.bodyParams.visitor & { connectionData?: unknown; channelName?: string } = this.bodyParams.visitor; guest.connectionData = normalizeHttpHeaderData(this.request.headers); + guest.channelName = channelName; const visitor = await LivechatTyped.registerGuest(guest); if (!visitor) { @@ -290,7 +301,7 @@ API.v1.addRoute( }, roomInfo: { source: { - type: isWidget(this.request.headers) ? OmnichannelSourceType.WIDGET : OmnichannelSourceType.API, + type: channelName, }, }, }; diff --git a/apps/meteor/app/livechat/server/methods/sendMessageLivechat.ts b/apps/meteor/app/livechat/server/methods/sendMessageLivechat.ts index 6fac80397906f..2675c17ca99d0 100644 --- a/apps/meteor/app/livechat/server/methods/sendMessageLivechat.ts +++ b/apps/meteor/app/livechat/server/methods/sendMessageLivechat.ts @@ -42,19 +42,26 @@ export const sendMessageLivechat = async ({ }), ); - const guest = await LivechatVisitors.getVisitorByToken(token, { - projection: { - name: 1, - username: 1, - department: 1, - token: 1, + const guest = await LivechatVisitors.getVisitorByTokenAndChannelName( + { token, channelName: OmnichannelSourceType.API }, + { + projection: { + name: 1, + username: 1, + department: 1, + token: 1, + }, }, - }); + ); if (!guest) { throw new Meteor.Error('invalid-token'); } + if (!guest.channelName) { + await LivechatVisitors.setChannelNameById(guest._id, OmnichannelSourceType.API); + } + if (settings.get('Livechat_enable_message_character_limit') && msg.length > parseInt(settings.get('Livechat_message_character_limit'))) { throw new Meteor.Error('message-length-exceeds-character-limit'); } From be799bdfe569cbf692c404a8e911160db6690272 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 14 Oct 2024 16:01:32 -0300 Subject: [PATCH 06/23] feat: restrict visitors by channelName in the UI kit messages flow --- apps/meteor/ee/server/apps/communication/uikit.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/meteor/ee/server/apps/communication/uikit.ts b/apps/meteor/ee/server/apps/communication/uikit.ts index 0392076704d72..154c8aea7082f 100644 --- a/apps/meteor/ee/server/apps/communication/uikit.ts +++ b/apps/meteor/ee/server/apps/communication/uikit.ts @@ -61,9 +61,10 @@ router.use(authenticationMiddleware({ rejectUnauthorized: false })); router.use(async (req: Request, res, next) => { const { 'x-visitor-token': visitorToken } = req.headers; + const { id: appId } = req.params; if (visitorToken) { - req.body.visitor = await Apps.getConverters()?.get('visitors').convertByToken(visitorToken); + req.body.visitor = await Apps.getConverters()?.get('visitors').convertByTokenAndChannelName(visitorToken, appId); } if (!req.user && !req.body.visitor) { From 92a6cfc3ef055ba1332db873e66440a765c85166 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 14 Oct 2024 18:08:11 -0300 Subject: [PATCH 07/23] replace channelName by source --- .../app/apps/server/bridges/livechat.ts | 19 +++++---- .../app/apps/server/converters/visitors.js | 9 +++-- .../app/livechat/imports/server/rest/sms.ts | 17 +++++--- .../livechat/imports/server/rest/upload.ts | 8 ++-- .../app/livechat/server/api/lib/livechat.ts | 8 ++-- .../app/livechat/server/api/v1/message.ts | 25 ++++++------ .../app/livechat/server/lib/LivechatTyped.ts | 8 ++-- .../server/methods/sendMessageLivechat.ts | 8 ++-- .../app/livechat/server/sendMessageBySMS.ts | 13 +++--- .../ee/server/apps/communication/uikit.ts | 2 +- .../EmailInbox/EmailInbox_Incoming.ts | 9 +++-- .../server/models/raw/LivechatVisitors.ts | 40 +++++++++---------- .../src/converters/IAppVisitorsConverter.ts | 2 +- packages/core-typings/src/ILivechatVisitor.ts | 3 +- .../src/models/ILivechatVisitorsModel.ts | 20 +++++----- 15 files changed, 105 insertions(+), 86 deletions(-) diff --git a/apps/meteor/app/apps/server/bridges/livechat.ts b/apps/meteor/app/apps/server/bridges/livechat.ts index bfc0ade99887c..607923d558b9c 100644 --- a/apps/meteor/app/apps/server/bridges/livechat.ts +++ b/apps/meteor/app/apps/server/bridges/livechat.ts @@ -53,9 +53,14 @@ export class AppLivechatBridge extends LivechatBridge { const livechatMessage = appMessage as ILivechatMessage | undefined; if (guest) { - const fullVisitor = await LivechatVisitors.findOneEnabledByIdAndChannelName({ _id: guest._id, channelName: appId }); - if (!fullVisitor?.channelName) { - await LivechatVisitors.setChannelNameById(guest._id, appId); + const visitorSource = { + type: OmnichannelSourceType.APP, + id: appId, + alias: this.orch.getManager()?.getOneById(appId)?.getNameSlug(), + }; + const fullVisitor = await LivechatVisitors.findOneEnabledByIdAndSource({ _id: guest._id, source: visitorSource }); + if (!fullVisitor?.source) { + await LivechatVisitors.setSourceById(guest._id, visitorSource); } } @@ -293,7 +298,7 @@ export class AppLivechatBridge extends LivechatBridge { } return Promise.all( - (await LivechatVisitors.findEnabledByChannelName(appId, query).toArray()).map( + (await LivechatVisitors.findEnabledBySource(appId, query).toArray()).map( async (visitor) => visitor && this.orch.getConverters()?.get('visitors').convertVisitor(visitor), ), ); @@ -311,7 +316,7 @@ export class AppLivechatBridge extends LivechatBridge { return this.orch .getConverters() ?.get('visitors') - .convertVisitor(await LivechatVisitors.findOneGuestByEmailAddress(email, appId)); + .convertVisitor(await LivechatVisitors.findOneGuestByEmailAddress(email, { type: OmnichannelSourceType.APP, id: appId })); } protected async findVisitorByToken(token: string, appId: string): Promise { @@ -320,7 +325,7 @@ export class AppLivechatBridge extends LivechatBridge { return this.orch .getConverters() ?.get('visitors') - .convertVisitor(await LivechatVisitors.getVisitorByTokenAndChannelName({ token, channelName: appId })); + .convertVisitor(await LivechatVisitors.getVisitorByTokenAndSource({ token, source: { type: OmnichannelSourceType.APP, id: appId } })); } protected async findVisitorByPhoneNumber(phoneNumber: string, appId: string): Promise { @@ -329,7 +334,7 @@ export class AppLivechatBridge extends LivechatBridge { return this.orch .getConverters() ?.get('visitors') - .convertVisitor(await LivechatVisitors.findOneVisitorByPhone(phoneNumber, appId)); + .convertVisitor(await LivechatVisitors.findOneVisitorByPhone(phoneNumber, { type: OmnichannelSourceType.APP, id: appId })); } protected async findDepartmentByIdOrName(value: string, appId: string): Promise { diff --git a/apps/meteor/app/apps/server/converters/visitors.js b/apps/meteor/app/apps/server/converters/visitors.js index 7d01b010ac6ec..41db236b2c7de 100644 --- a/apps/meteor/app/apps/server/converters/visitors.js +++ b/apps/meteor/app/apps/server/converters/visitors.js @@ -1,3 +1,4 @@ +import { OmnichannelSourceType } from '@rocket.chat/core-typings'; import { LivechatVisitors } from '@rocket.chat/models'; import { transformMappedData } from './transformMappedData'; @@ -9,7 +10,7 @@ export class AppVisitorsConverter { } async convertById(id, appId) { - const visitor = await LivechatVisitors.findOneEnabledByIdAndChannelName({ _id: id, channelName: appId }); + const visitor = await LivechatVisitors.findOneEnabledByIdAndSource({ _id: id, source: { type: OmnichannelSourceType.APP, id: appId } }); return this.convertVisitor(visitor); } @@ -20,8 +21,8 @@ export class AppVisitorsConverter { return this.convertVisitor(visitor); } - async convertByTokenAndChannelName(token, appId) { - const visitor = await LivechatVisitors.getVisitorByTokenAndChannelName({ token, channelName: appId }); + async convertByTokenAndSource(token, appId) { + const visitor = await LivechatVisitors.getVisitorByTokenAndSource({ token, source: { type: OmnichannelSourceType.APP, id: appId } }); return this.convertVisitor(visitor); } @@ -43,7 +44,7 @@ export class AppVisitorsConverter { livechatData: 'livechatData', status: 'status', contactId: 'contactId', - channelName: 'channelName', + source: 'source', }; return transformMappedData(visitor, map); diff --git a/apps/meteor/app/livechat/imports/server/rest/sms.ts b/apps/meteor/app/livechat/imports/server/rest/sms.ts index 6c820e958c033..82e1f45f0cde3 100644 --- a/apps/meteor/app/livechat/imports/server/rest/sms.ts +++ b/apps/meteor/app/livechat/imports/server/rest/sms.ts @@ -6,6 +6,7 @@ import type { MessageAttachment, ServiceData, FileAttachmentProps, + IOmnichannelSource, } from '@rocket.chat/core-typings'; import { OmnichannelSourceType } from '@rocket.chat/core-typings'; import { Logger } from '@rocket.chat/logger'; @@ -55,11 +56,17 @@ const defineDepartment = async (idOrName?: string) => { return department?._id; }; -const defineVisitor = async (smsNumber: string, targetDepartment?: string) => { - const visitor = await LivechatVisitors.findOneVisitorByPhone(smsNumber, OmnichannelSourceType.SMS); - let data: { token: string; channelName: string; department?: string } = { +const defineVisitor = async (smsNumber: string, serviceName: string, destination: string, targetDepartment?: string) => { + const visitorSource = { + type: OmnichannelSourceType.SMS, + alias: serviceName, + destination, + }; + + const visitor = await LivechatVisitors.findOneVisitorByPhone(smsNumber, visitorSource); + let data: { token: string; source: IOmnichannelSource; department?: string } = { token: visitor?.token || Random.id(), - channelName: OmnichannelSourceType.SMS, + source: visitorSource, }; if (!visitor) { @@ -118,7 +125,7 @@ API.v1.addRoute('livechat/sms-incoming/:service', { targetDepartment = await defineDepartment(smsDepartment); } - const visitor = await defineVisitor(sms.from, targetDepartment); + const visitor = await defineVisitor(sms.from, service, sms.to, targetDepartment); if (!visitor) { return API.v1.success(SMSService.error(new Error('Invalid visitor'))); } diff --git a/apps/meteor/app/livechat/imports/server/rest/upload.ts b/apps/meteor/app/livechat/imports/server/rest/upload.ts index 058dd6389dce3..6bd2c9aa5ce0f 100644 --- a/apps/meteor/app/livechat/imports/server/rest/upload.ts +++ b/apps/meteor/app/livechat/imports/server/rest/upload.ts @@ -15,7 +15,7 @@ API.v1.addRoute('livechat/upload/:rid', { if (!this.request.headers['x-visitor-token']) { return API.v1.unauthorized(); } - const channelName = isWidget(this.request.headers) ? OmnichannelSourceType.WIDGET : OmnichannelSourceType.API; + const sourceType = isWidget(this.request.headers) ? OmnichannelSourceType.WIDGET : OmnichannelSourceType.API; const canUpload = settings.get('Livechat_fileupload_enabled') && settings.get('FileUpload_Enabled'); @@ -26,7 +26,7 @@ API.v1.addRoute('livechat/upload/:rid', { } const visitorToken = this.request.headers['x-visitor-token']; - const visitor = await LivechatVisitors.getVisitorByTokenAndChannelName({ token: visitorToken as string, channelName }); + const visitor = await LivechatVisitors.getVisitorByTokenAndSource({ token: visitorToken as string, source: { type: sourceType } }); if (!visitor) { return API.v1.unauthorized(); @@ -79,8 +79,8 @@ API.v1.addRoute('livechat/upload/:rid', { return API.v1.failure('Invalid file'); } - if (!visitor.channelName) { - await LivechatVisitors.setChannelNameById(visitor._id, channelName); + if (!visitor.source) { + await LivechatVisitors.setSourceById(visitor._id, { type: sourceType }); } uploadedFile.description = fields.description; diff --git a/apps/meteor/app/livechat/server/api/lib/livechat.ts b/apps/meteor/app/livechat/server/api/lib/livechat.ts index bb36d263a7d2e..9158de1013d7a 100644 --- a/apps/meteor/app/livechat/server/api/lib/livechat.ts +++ b/apps/meteor/app/livechat/server/api/lib/livechat.ts @@ -48,7 +48,7 @@ async function findDepartments( })); } -export function findGuest(token: string, channelName?: string): Promise { +export function findGuest(token: string, sourceType?: string): Promise { const projection = { name: 1, username: 1, @@ -57,11 +57,11 @@ export function findGuest(token: string, channelName?: string): Promise> & { +type RegisterGuestType = Partial> & { id?: string; connectionData?: any; email?: string; @@ -654,7 +654,7 @@ class LivechatClass { username, connectionData, status = UserStatus.ONLINE, - channelName, + source, }: RegisterGuestType): Promise { check(token, String); check(id, Match.Maybe(String)); @@ -664,7 +664,7 @@ class LivechatClass { const visitorDataToUpdate: Partial & { userAgent?: string; ip?: string; host?: string } = { token, status, - channelName, + source, ...(phone?.number ? { phone: [{ phoneNumber: phone.number }] } : {}), ...(name ? { name } : {}), }; @@ -710,7 +710,7 @@ class LivechatClass { visitorDataToUpdate.username = username || (await LivechatVisitors.getNextVisitorUsername()); visitorDataToUpdate.status = status; visitorDataToUpdate.ts = new Date(); - visitorDataToUpdate.channelName = channelName; + visitorDataToUpdate.source = source; if (settings.get('Livechat_Allow_collect_and_store_HTTP_header_informations') && Livechat.isValidObject(connectionData)) { Livechat.logger.debug(`Saving connection data for visitor ${token}`); diff --git a/apps/meteor/app/livechat/server/methods/sendMessageLivechat.ts b/apps/meteor/app/livechat/server/methods/sendMessageLivechat.ts index 2675c17ca99d0..7f3f748d950e6 100644 --- a/apps/meteor/app/livechat/server/methods/sendMessageLivechat.ts +++ b/apps/meteor/app/livechat/server/methods/sendMessageLivechat.ts @@ -42,8 +42,8 @@ export const sendMessageLivechat = async ({ }), ); - const guest = await LivechatVisitors.getVisitorByTokenAndChannelName( - { token, channelName: OmnichannelSourceType.API }, + const guest = await LivechatVisitors.getVisitorByTokenAndSource( + { token, source: { type: OmnichannelSourceType.API } }, { projection: { name: 1, @@ -58,8 +58,8 @@ export const sendMessageLivechat = async ({ throw new Meteor.Error('invalid-token'); } - if (!guest.channelName) { - await LivechatVisitors.setChannelNameById(guest._id, OmnichannelSourceType.API); + if (!guest.source) { + await LivechatVisitors.setSourceById(guest._id, { type: OmnichannelSourceType.API }); } if (settings.get('Livechat_enable_message_character_limit') && msg.length > parseInt(settings.get('Livechat_message_character_limit'))) { diff --git a/apps/meteor/app/livechat/server/sendMessageBySMS.ts b/apps/meteor/app/livechat/server/sendMessageBySMS.ts index f60db26862b17..9874165a7c66e 100644 --- a/apps/meteor/app/livechat/server/sendMessageBySMS.ts +++ b/apps/meteor/app/livechat/server/sendMessageBySMS.ts @@ -1,4 +1,5 @@ import { OmnichannelIntegration } from '@rocket.chat/core-services'; +import type { IOmnichannelSource } from '@rocket.chat/core-typings'; import { isEditedMessage, OmnichannelSourceType } from '@rocket.chat/core-typings'; import { LivechatVisitors } from '@rocket.chat/models'; @@ -55,17 +56,19 @@ callbacks.add( return message; } - const visitor = await LivechatVisitors.getVisitorByTokenAndChannelName( - { token: room.v.token, channelName: OmnichannelSourceType.SMS }, - { projection: { phone: 1, channelName: 1 } }, + const visitorSource: IOmnichannelSource = { type: OmnichannelSourceType.SMS, alias: service }; + const visitor = await LivechatVisitors.getVisitorByTokenAndSource( + { token: room.v.token, source: visitorSource }, + { projection: { phone: 1, source: 1 } }, ); if (!visitor?.phone || visitor.phone.length === 0) { return message; } + visitorSource.destination = visitor.phone[0].phoneNumber; - if (!visitor.channelName) { - await LivechatVisitors.setChannelNameById(visitor._id, OmnichannelSourceType.SMS); + if (!visitor.source) { + await LivechatVisitors.setSourceById(visitor._id, visitorSource); } try { diff --git a/apps/meteor/ee/server/apps/communication/uikit.ts b/apps/meteor/ee/server/apps/communication/uikit.ts index 154c8aea7082f..87bb37965d075 100644 --- a/apps/meteor/ee/server/apps/communication/uikit.ts +++ b/apps/meteor/ee/server/apps/communication/uikit.ts @@ -64,7 +64,7 @@ router.use(async (req: Request, res, next) => { const { id: appId } = req.params; if (visitorToken) { - req.body.visitor = await Apps.getConverters()?.get('visitors').convertByTokenAndChannelName(visitorToken, appId); + req.body.visitor = await Apps.getConverters()?.get('visitors').convertByTokenAndSource(visitorToken, appId); } if (!req.user && !req.body.visitor) { diff --git a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts index 1b549d103e261..b0c0b67076dcf 100644 --- a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts +++ b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts @@ -24,7 +24,7 @@ type FileAttachment = VideoAttachmentProps & ImageAttachmentProps & AudioAttachm const language = settings.get('Language') || 'en'; const t = i18n.getFixedT(language); -async function getGuestByEmail(email: string, name: string, department = ''): Promise { +async function getGuestByEmail(email: string, name: string, inbox: string, department = ''): Promise { const guest = await LivechatVisitors.findOneGuestByEmailAddress(email, OmnichannelSourceType.EMAIL); if (guest) { @@ -37,8 +37,9 @@ async function getGuestByEmail(email: string, name: string, department = ''): Pr await LivechatTyped.setDepartmentForGuest({ token: guest.token, department }); return LivechatVisitors.findOneEnabledById(guest._id, {}); } - if (!guest.channelName) { - await LivechatVisitors.setChannelNameById(guest._id, OmnichannelSourceType.EMAIL); + if (!guest.source) { + const source = { type: OmnichannelSourceType.EMAIL, id: inbox, alias: 'email-inbox' }; + await LivechatVisitors.setSourceById(guest._id, source); } return guest; } @@ -109,7 +110,7 @@ export async function onEmailReceived(email: ParsedMail, inbox: string, departme const references = typeof email.references === 'string' ? [email.references] : email.references; const initialRef = [email.messageId, email.inReplyTo].filter(Boolean) as string[]; const thread = (references?.length ? references : []).flatMap((t: string) => t.split(',')).concat(initialRef); - const guest = await getGuestByEmail(email.from.value[0].address, email.from.value[0].name, department); + const guest = await getGuestByEmail(email.from.value[0].address, email.from.value[0].name, inbox, department); if (!guest) { logger.error(`No visitor found for ${email.from.value[0].address}`); diff --git a/apps/meteor/server/models/raw/LivechatVisitors.ts b/apps/meteor/server/models/raw/LivechatVisitors.ts index f84593bb60944..46f68ce6cc437 100644 --- a/apps/meteor/server/models/raw/LivechatVisitors.ts +++ b/apps/meteor/server/models/raw/LivechatVisitors.ts @@ -1,4 +1,4 @@ -import type { ILivechatVisitor, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; +import type { ILivechatVisitor, IOmnichannelSource, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; import type { FindPaginated, ILivechatVisitorsModel } from '@rocket.chat/model-typings'; import { Settings } from '@rocket.chat/models'; import { escapeRegExp } from '@rocket.chat/string-helpers'; @@ -42,21 +42,21 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL ]; } - findOneVisitorByPhone(phone: string, channelName?: string): Promise { - const emptyChannelNameFilter = { channelName: { $exists: false } }; + findOneVisitorByPhone(phone: string, source?: IOmnichannelSource): Promise { + const emptySourceFilter = { source: { $exists: false } }; const query = { 'phone.phoneNumber': phone, - ...(channelName ? { $or: [{ channelName }, emptyChannelNameFilter] } : emptyChannelNameFilter), + ...(source ? { $or: [{ source }, emptySourceFilter] } : emptySourceFilter), }; return this.findOne(query); } - findOneGuestByEmailAddress(emailAddress: string, channelName?: string): Promise { - const emptyChannelNameFilter = { channelName: { $exists: false } }; + findOneGuestByEmailAddress(emailAddress: string, source?: IOmnichannelSource): Promise { + const emptySourceFilter = { source: { $exists: false } }; const query = { 'visitorEmails.address': String(emailAddress).toLowerCase(), - ...(channelName ? { $or: [{ channelName }, emptyChannelNameFilter] } : emptyChannelNameFilter), + ...(source ? { $or: [{ source }, emptySourceFilter] } : emptySourceFilter), }; return this.findOne(query); @@ -74,15 +74,15 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL return this.find(query, options); } - findEnabledByChannelName( - channelName: string, + findEnabledBySource( + source: IOmnichannelSource, query: Filter, options?: FindOptions, ): FindCursor { return this.find( { ...query, - $or: [{ channelName }, { channelName: { $exists: false } }], + $or: [{ source }, { source: { $exists: false } }], disabled: { $ne: true }, }, options, @@ -98,15 +98,15 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL return this.findOne(query, options); } - findOneEnabledByIdAndChannelName( - { _id, channelName }: { _id: string; channelName: string }, + findOneEnabledByIdAndSource( + { _id, source }: { _id: string; source: IOmnichannelSource }, options?: FindOptions, ): Promise { - const emptyChannelNameFilter = { channelName: { $exists: false } }; + const emptySourceFilter = { source: { $exists: false } }; const query = { _id, disabled: { $ne: true }, - ...(channelName ? { $or: [{ channelName }, emptyChannelNameFilter] } : emptyChannelNameFilter), + ...(source ? { $or: [{ source }, emptySourceFilter] } : emptySourceFilter), }; return this.findOne(query, options); @@ -129,14 +129,14 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL return this.findOne(query, options); } - getVisitorByTokenAndChannelName( - { token, channelName }: { token: string; channelName?: string }, + getVisitorByTokenAndSource( + { token, source }: { token: string; source?: IOmnichannelSource }, options: FindOptions, ): Promise { - const emptyChannelNameFilter = { channelName: { $exists: false } }; + const emptySourceFilter = { source: { $exists: false } }; const query = { token, - ...(channelName ? { $or: [{ channelName }, emptyChannelNameFilter] } : emptyChannelNameFilter), + ...(source ? { $or: [{ source }, emptySourceFilter] } : emptySourceFilter), }; return this.findOne(query, options); @@ -508,12 +508,12 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL ); } - setChannelNameById(_id: string, channelName: Required): Promise { + setSourceById(_id: string, source: Required): Promise { return this.updateOne( { _id }, { $set: { - channelName, + source, }, }, ); diff --git a/packages/apps/src/converters/IAppVisitorsConverter.ts b/packages/apps/src/converters/IAppVisitorsConverter.ts index fe9fa8eb10da9..8feb9925c4494 100644 --- a/packages/apps/src/converters/IAppVisitorsConverter.ts +++ b/packages/apps/src/converters/IAppVisitorsConverter.ts @@ -5,7 +5,7 @@ import type { IAppsVisitor } from '../AppsEngine'; export interface IAppVisitorsConverter { convertById(visitorId: ILivechatVisitor['_id'], appId?: string): Promise; convertByToken(token: string): Promise; - convertByTokenAndChannelName(token: string, appId?: string): Promise; + convertByTokenAndSource(token: string, appId?: string): Promise; convertVisitor(visitor: undefined | null): Promise; convertVisitor(visitor: ILivechatVisitor): Promise; convertVisitor(visitor: ILivechatVisitor | undefined | null): Promise; diff --git a/packages/core-typings/src/ILivechatVisitor.ts b/packages/core-typings/src/ILivechatVisitor.ts index 580abcd820ecb..2512496e7cc04 100644 --- a/packages/core-typings/src/ILivechatVisitor.ts +++ b/packages/core-typings/src/ILivechatVisitor.ts @@ -1,4 +1,5 @@ import type { IRocketChatRecord } from './IRocketChatRecord'; +import type { IOmnichannelSource } from './IRoom'; import type { UserStatus } from './UserStatus'; export interface IVisitorPhone { @@ -50,7 +51,7 @@ export interface ILivechatVisitor extends IRocketChatRecord { activity?: string[]; disabled?: boolean; contactId?: string; - channelName: string; + source: IOmnichannelSource; } export interface ILivechatVisitorDTO { diff --git a/packages/model-typings/src/models/ILivechatVisitorsModel.ts b/packages/model-typings/src/models/ILivechatVisitorsModel.ts index 73f05b299e388..31e83b96f697a 100644 --- a/packages/model-typings/src/models/ILivechatVisitorsModel.ts +++ b/packages/model-typings/src/models/ILivechatVisitorsModel.ts @@ -1,4 +1,4 @@ -import type { ILivechatVisitor } from '@rocket.chat/core-typings'; +import type { ILivechatVisitor, IOmnichannelSource } from '@rocket.chat/core-typings'; import type { AggregationCursor, FindCursor, @@ -16,8 +16,8 @@ import type { FindPaginated, IBaseModel } from './IBaseModel'; export interface ILivechatVisitorsModel extends IBaseModel { findById(_id: string, options?: FindOptions): FindCursor; getVisitorByToken(token: string, options?: FindOptions): Promise; - getVisitorByTokenAndChannelName( - { token, channelName }: { token: string; channelName?: string }, + getVisitorByTokenAndSource( + { token, source }: { token: string; source?: IOmnichannelSource }, options?: FindOptions, ): Promise; getVisitorsBetweenDate({ start, end, department }: { start: Date; end: Date; department?: string }): FindCursor; @@ -49,9 +49,9 @@ export interface ILivechatVisitorsModel extends IBaseModel { updateLivechatDataByToken(token: string, key: string, value: unknown, overwrite: boolean): Promise; - findOneGuestByEmailAddress(emailAddress: string, channelName?: string): Promise; + findOneGuestByEmailAddress(emailAddress: string, source?: IOmnichannelSource): Promise; - findOneVisitorByPhone(phone: string, channelName?: string): Promise; + findOneVisitorByPhone(phone: string, source?: IOmnichannelSource): Promise; removeDepartmentById(_id: string): Promise; @@ -71,15 +71,15 @@ export interface ILivechatVisitorsModel extends IBaseModel { findOneEnabledById(_id: string, options?: FindOptions): Promise; - findOneEnabledByIdAndChannelName( - { _id, channelName }: { _id: string; channelName: string }, + findOneEnabledByIdAndSource( + { _id, source }: { _id: string; source: IOmnichannelSource }, options?: FindOptions, ): Promise; disableById(_id: string): Promise; - findEnabledByChannelName( - channelName: string, + findEnabledBySource( + source: IOmnichannelSource, query: Filter, options?: FindOptions, ): FindCursor; @@ -90,5 +90,5 @@ export interface ILivechatVisitorsModel extends IBaseModel { data: { name?: string; username?: string; email?: string; phone?: string; livechatData: { [k: string]: any } }, ): Promise; setLastChatById(_id: string, lastChat: Required): Promise; - setChannelNameById(_id: string, channelName: Required): Promise; + setSourceById(_id: string, source: ILivechatVisitor['source']): Promise; } From d7f5f85aec3b31d82d69797c5adf76ebebd70087 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 14 Oct 2024 18:14:49 -0300 Subject: [PATCH 08/23] Remove channelName --- apps/meteor/app/apps/server/bridges/livechat.ts | 2 +- apps/meteor/app/apps/server/converters/visitors.js | 2 +- apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts | 4 ++-- apps/meteor/server/models/raw/LivechatVisitors.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/meteor/app/apps/server/bridges/livechat.ts b/apps/meteor/app/apps/server/bridges/livechat.ts index 607923d558b9c..ff2c81fcc7807 100644 --- a/apps/meteor/app/apps/server/bridges/livechat.ts +++ b/apps/meteor/app/apps/server/bridges/livechat.ts @@ -298,7 +298,7 @@ export class AppLivechatBridge extends LivechatBridge { } return Promise.all( - (await LivechatVisitors.findEnabledBySource(appId, query).toArray()).map( + (await LivechatVisitors.findEnabledBySource({ type: OmnichannelSourceType.APP, id: appId }, query).toArray()).map( async (visitor) => visitor && this.orch.getConverters()?.get('visitors').convertVisitor(visitor), ), ); diff --git a/apps/meteor/app/apps/server/converters/visitors.js b/apps/meteor/app/apps/server/converters/visitors.js index 41db236b2c7de..54c8ef42ffdef 100644 --- a/apps/meteor/app/apps/server/converters/visitors.js +++ b/apps/meteor/app/apps/server/converters/visitors.js @@ -64,7 +64,7 @@ export class AppVisitorsConverter { livechatData: visitor.livechatData, status: visitor.status || 'online', contactId: visitor.contactId, - channelName: visitor.channelName, + source: visitor.source, ...(visitor.visitorEmails && { visitorEmails: visitor.visitorEmails }), ...(visitor.department && { department: visitor.department }), }; diff --git a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts index b0c0b67076dcf..3a9bd5e85f5b9 100644 --- a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts +++ b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts @@ -25,7 +25,7 @@ const language = settings.get('Language') || 'en'; const t = i18n.getFixedT(language); async function getGuestByEmail(email: string, name: string, inbox: string, department = ''): Promise { - const guest = await LivechatVisitors.findOneGuestByEmailAddress(email, OmnichannelSourceType.EMAIL); + const guest = await LivechatVisitors.findOneGuestByEmailAddress(email, { type: OmnichannelSourceType.EMAIL, id: inbox }); if (guest) { if (guest.department !== department) { @@ -49,7 +49,7 @@ async function getGuestByEmail(email: string, name: string, inbox: string, depar name: name || email, email, department, - channelName: OmnichannelSourceType.EMAIL, + source: { type: OmnichannelSourceType.EMAIL, id: inbox, alias: 'email-inbox' }, }); if (!livechatVisitor) { diff --git a/apps/meteor/server/models/raw/LivechatVisitors.ts b/apps/meteor/server/models/raw/LivechatVisitors.ts index 46f68ce6cc437..f1ef5d7beb1c1 100644 --- a/apps/meteor/server/models/raw/LivechatVisitors.ts +++ b/apps/meteor/server/models/raw/LivechatVisitors.ts @@ -30,7 +30,7 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL protected modelIndexes(): IndexDescription[] { return [ { key: { token: 1 } }, - { key: { token: 1, channelName: 1 } }, + { key: { 'token': 1, 'source.type': 1 } }, { key: { 'phone.phoneNumber': 1 }, sparse: true }, { key: { 'visitorEmails.address': 1 }, sparse: true }, { key: { name: 1 }, sparse: true }, From 386adbec3e94d5310ff5857e1fed93804a233c49 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 14 Oct 2024 19:26:17 -0300 Subject: [PATCH 09/23] do not use target phone (destination) to differ visitors --- apps/meteor/app/livechat/imports/server/rest/sms.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/meteor/app/livechat/imports/server/rest/sms.ts b/apps/meteor/app/livechat/imports/server/rest/sms.ts index 82e1f45f0cde3..dabb070674464 100644 --- a/apps/meteor/app/livechat/imports/server/rest/sms.ts +++ b/apps/meteor/app/livechat/imports/server/rest/sms.ts @@ -57,13 +57,13 @@ const defineDepartment = async (idOrName?: string) => { }; const defineVisitor = async (smsNumber: string, serviceName: string, destination: string, targetDepartment?: string) => { - const visitorSource = { + const visitorSource: IOmnichannelSource = { type: OmnichannelSourceType.SMS, alias: serviceName, - destination, }; const visitor = await LivechatVisitors.findOneVisitorByPhone(smsNumber, visitorSource); + visitorSource.destination = destination; let data: { token: string; source: IOmnichannelSource; department?: string } = { token: visitor?.token || Random.id(), source: visitorSource, From afc473ff90bb6ae58a33f1b930c4ad31580a2f61 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 14 Oct 2024 19:34:36 -0300 Subject: [PATCH 10/23] tests: add end-to-end tests --- .../tests/end-to-end/api/livechat/00-rooms.ts | 53 +++++++++++++++++++ .../api/livechat/06-integrations.ts | 40 ++++++++++++++ .../end-to-end/api/livechat/11-livechat.ts | 16 ++++++ 3 files changed, 109 insertions(+) diff --git a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts index 43454c5115dc9..f8a198fbdeb35 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts @@ -1256,6 +1256,32 @@ describe('LIVECHAT - rooms', () => { await deleteVisitor(visitor.token); }); + it("should set visitor's source as API after uploading a file", async () => { + await updateSetting('FileUpload_Enabled', true); + await updateSetting('Livechat_fileupload_enabled', true); + const visitor = await createVisitor(); + const room = await createLivechatRoom(visitor.token); + await request + .post(api(`livechat/upload/${room._id}`)) + .set(credentials) + .set('x-visitor-token', visitor.token) + .attach('file', fs.createReadStream(path.join(__dirname, '../../../data/livechat/sample.png'))) + .expect('Content-Type', 'application/json') + .expect(200); + + const { body } = await request + .get(api('livechat/visitors.info')) + .query({ visitorId: visitor._id }) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + expect(body).to.have.property('visitor').and.to.be.an('object'); + expect(body.visitor).to.have.property('source').and.to.be.an('object'); + expect(body.visitor.source).to.have.property('type', 'api'); + + await deleteVisitor(visitor.token); + }); + it('should allow visitor to download file', async () => { const visitor = await createVisitor(); const room = await createLivechatRoom(visitor.token); @@ -1651,6 +1677,33 @@ describe('LIVECHAT - rooms', () => { expect(body.messages[1]).to.have.property('username', visitor.username); await deleteVisitor(visitor.token); }); + + it("should set visitor's source as API after creating messages in a room", async () => { + const visitor = await createVisitor(); + const room = await createLivechatRoom(visitor.token); + await sendMessage(room._id, 'Hello', visitor.token); + + const { body } = await request + .post(api('livechat/messages')) + .set(credentials) + .send({ visitor: { token: visitor.token }, messages: [{ msg: 'Hello' }, { msg: 'Hello 2' }] }) + .expect('Content-Type', 'application/json') + .expect(200); + expect(body).to.have.property('success', true); + expect(body).to.have.property('messages').of.length(2); + + const { body: getVisitorResponse } = await request + .get(api('livechat/visitors.info')) + .query({ visitorId: visitor._id }) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(getVisitorResponse).to.have.property('visitor').and.to.be.an('object'); + expect(getVisitorResponse.visitor).to.have.property('source').and.to.be.an('object'); + expect(getVisitorResponse.visitor.source).to.have.property('type', 'api'); + await deleteVisitor(visitor.token); + }); }); describe('livechat/transfer.history/:rid', () => { diff --git a/apps/meteor/tests/end-to-end/api/livechat/06-integrations.ts b/apps/meteor/tests/end-to-end/api/livechat/06-integrations.ts index bc4d6fa04dc41..957152976f14b 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/06-integrations.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/06-integrations.ts @@ -115,6 +115,46 @@ describe('LIVECHAT - Integrations', () => { expect(res).to.have.property('text', ''); }); }); + + it("should set visitor's source as SMS after sending a message", async () => { + await updateSetting('SMS_Default_Omnichannel_Department', ''); + await updateSetting('SMS_Service', 'twilio'); + + const token = `${new Date().getTime()}-test2`; + const phone = new Date().getTime().toString(); + const { body: createVisitorResponse } = await request.post(api('livechat/visitor')).send({ visitor: { token, phone } }); + expect(createVisitorResponse).to.have.property('success', true); + expect(createVisitorResponse).to.have.property('visitor').and.to.be.an('object'); + expect(createVisitorResponse.visitor).to.have.property('_id'); + const visitorId = createVisitorResponse.visitor._id; + + await request + .post(api('livechat/sms-incoming/twilio')) + .set(credentials) + .send({ + From: phone, + To: '+123456789', + Body: 'Hello', + }) + .expect('Content-Type', 'text/xml') + .expect(200) + .expect((res: Response) => { + expect(res).to.have.property('text', ''); + }); + + const { body } = await request + .get(api('livechat/visitors.info')) + .query({ visitorId }) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(body).to.have.property('visitor').and.to.be.an('object'); + expect(body.visitor).to.have.property('source').and.to.be.an('object'); + expect(body.visitor.source).to.have.property('type', 'sms'); + expect(body.visitor.source).to.have.property('alias', 'twilio'); + expect(body.visitor.source).to.have.property('destination', '+123456789'); + }); }); }); diff --git a/apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts b/apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts index 7ce582025538a..b3952be5043e0 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts @@ -558,6 +558,22 @@ describe('LIVECHAT - Utils', () => { const room = await createLivechatRoom(visitor.token); await request.post(api('livechat/message')).set(credentials).send({ token: visitor.token, rid: room._id, msg: 'test' }).expect(200); }); + it("should set visitor's source as API after sending a message", async () => { + const visitor = await createVisitor(); + const room = await createLivechatRoom(visitor.token); + await request.post(api('livechat/message')).set(credentials).send({ token: visitor.token, rid: room._id, msg: 'test' }).expect(200); + + const { body } = await request + .get(api('livechat/visitors.info')) + .query({ visitorId: visitor._id }) + .set(credentials) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(body).to.have.property('visitor').and.to.be.an('object'); + expect(body.visitor).to.have.property('source').and.to.be.an('object'); + expect(body.visitor.source).to.have.property('type', 'api'); + }); }); describe('[GET] livechat/message/:_id', () => { From 0c29c2a518f3c026c4e0a069e222bb710836cb0c Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 14 Oct 2024 19:37:41 -0300 Subject: [PATCH 11/23] fix typecheck --- apps/meteor/app/livechat/server/api/lib/livechat.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/meteor/app/livechat/server/api/lib/livechat.ts b/apps/meteor/app/livechat/server/api/lib/livechat.ts index 9158de1013d7a..65918a93bff1e 100644 --- a/apps/meteor/app/livechat/server/api/lib/livechat.ts +++ b/apps/meteor/app/livechat/server/api/lib/livechat.ts @@ -1,4 +1,4 @@ -import type { ILivechatAgent, ILivechatDepartment, ILivechatTrigger, ILivechatVisitor, IOmnichannelRoom } from '@rocket.chat/core-typings'; +import type { ILivechatAgent, ILivechatDepartment, ILivechatTrigger, ILivechatVisitor, IOmnichannelRoom, OmnichannelSourceType } from '@rocket.chat/core-typings'; import { License } from '@rocket.chat/license'; import { EmojiCustom, LivechatTrigger, LivechatVisitors, LivechatRooms, LivechatDepartment } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; @@ -48,7 +48,7 @@ async function findDepartments( })); } -export function findGuest(token: string, sourceType?: string): Promise { +export function findGuest(token: string, sourceType?: OmnichannelSourceType): Promise { const projection = { name: 1, username: 1, From 9d03eaa9ca88f67c60146cb243404c6596564359 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Mon, 14 Oct 2024 20:40:32 -0300 Subject: [PATCH 12/23] fix lint and typecheck --- apps/meteor/app/livechat/server/api/lib/livechat.ts | 9 ++++++++- packages/core-typings/src/ILivechatVisitor.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/meteor/app/livechat/server/api/lib/livechat.ts b/apps/meteor/app/livechat/server/api/lib/livechat.ts index 65918a93bff1e..1844c9f4a168f 100644 --- a/apps/meteor/app/livechat/server/api/lib/livechat.ts +++ b/apps/meteor/app/livechat/server/api/lib/livechat.ts @@ -1,4 +1,11 @@ -import type { ILivechatAgent, ILivechatDepartment, ILivechatTrigger, ILivechatVisitor, IOmnichannelRoom, OmnichannelSourceType } from '@rocket.chat/core-typings'; +import type { + ILivechatAgent, + ILivechatDepartment, + ILivechatTrigger, + ILivechatVisitor, + IOmnichannelRoom, + OmnichannelSourceType, +} from '@rocket.chat/core-typings'; import { License } from '@rocket.chat/license'; import { EmojiCustom, LivechatTrigger, LivechatVisitors, LivechatRooms, LivechatDepartment } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; diff --git a/packages/core-typings/src/ILivechatVisitor.ts b/packages/core-typings/src/ILivechatVisitor.ts index 2512496e7cc04..3dc91911260df 100644 --- a/packages/core-typings/src/ILivechatVisitor.ts +++ b/packages/core-typings/src/ILivechatVisitor.ts @@ -51,7 +51,7 @@ export interface ILivechatVisitor extends IRocketChatRecord { activity?: string[]; disabled?: boolean; contactId?: string; - source: IOmnichannelSource; + source?: IOmnichannelSource; } export interface ILivechatVisitorDTO { From 2c6e1dcff322e3b132978fa58e0003e5dc22b4be Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Tue, 15 Oct 2024 00:21:15 -0300 Subject: [PATCH 13/23] fix nested queries --- .../app/apps/server/bridges/livechat.ts | 22 ++++++++++++++---- .../app/apps/server/converters/visitors.js | 10 ++++++-- .../app/livechat/imports/server/rest/sms.ts | 5 +++- .../livechat/imports/server/rest/upload.ts | 5 +++- .../app/livechat/server/api/lib/livechat.ts | 2 +- .../app/livechat/server/api/v1/message.ts | 5 +++- .../server/methods/sendMessageLivechat.ts | 3 ++- .../app/livechat/server/sendMessageBySMS.ts | 2 +- .../EmailInbox/EmailInbox_Incoming.ts | 5 +++- .../server/models/raw/LivechatVisitors.ts | 23 +++++++++---------- .../src/models/ILivechatVisitorsModel.ts | 12 +++++----- 11 files changed, 62 insertions(+), 32 deletions(-) diff --git a/apps/meteor/app/apps/server/bridges/livechat.ts b/apps/meteor/app/apps/server/bridges/livechat.ts index ff2c81fcc7807..675309b4da1a9 100644 --- a/apps/meteor/app/apps/server/bridges/livechat.ts +++ b/apps/meteor/app/apps/server/bridges/livechat.ts @@ -58,7 +58,10 @@ export class AppLivechatBridge extends LivechatBridge { id: appId, alias: this.orch.getManager()?.getOneById(appId)?.getNameSlug(), }; - const fullVisitor = await LivechatVisitors.findOneEnabledByIdAndSource({ _id: guest._id, source: visitorSource }); + const fullVisitor = await LivechatVisitors.findOneEnabledByIdAndSource({ + _id: guest._id, + sourceFilter: { 'source.type': visitorSource.type, 'source.id': visitorSource.id, 'source.alias': visitorSource.alias }, + }); if (!fullVisitor?.source) { await LivechatVisitors.setSourceById(guest._id, visitorSource); } @@ -298,7 +301,7 @@ export class AppLivechatBridge extends LivechatBridge { } return Promise.all( - (await LivechatVisitors.findEnabledBySource({ type: OmnichannelSourceType.APP, id: appId }, query).toArray()).map( + (await LivechatVisitors.findEnabledBySource({ 'source.type': OmnichannelSourceType.APP, 'source.id': appId }, query).toArray()).map( async (visitor) => visitor && this.orch.getConverters()?.get('visitors').convertVisitor(visitor), ), ); @@ -316,7 +319,9 @@ export class AppLivechatBridge extends LivechatBridge { return this.orch .getConverters() ?.get('visitors') - .convertVisitor(await LivechatVisitors.findOneGuestByEmailAddress(email, { type: OmnichannelSourceType.APP, id: appId })); + .convertVisitor( + await LivechatVisitors.findOneGuestByEmailAddress(email, { 'source.type': OmnichannelSourceType.APP, 'source.id': appId }), + ); } protected async findVisitorByToken(token: string, appId: string): Promise { @@ -325,7 +330,12 @@ export class AppLivechatBridge extends LivechatBridge { return this.orch .getConverters() ?.get('visitors') - .convertVisitor(await LivechatVisitors.getVisitorByTokenAndSource({ token, source: { type: OmnichannelSourceType.APP, id: appId } })); + .convertVisitor( + await LivechatVisitors.getVisitorByTokenAndSource({ + token, + sourceFilter: { 'source.type': OmnichannelSourceType.APP, 'source.id': appId }, + }), + ); } protected async findVisitorByPhoneNumber(phoneNumber: string, appId: string): Promise { @@ -334,7 +344,9 @@ export class AppLivechatBridge extends LivechatBridge { return this.orch .getConverters() ?.get('visitors') - .convertVisitor(await LivechatVisitors.findOneVisitorByPhone(phoneNumber, { type: OmnichannelSourceType.APP, id: appId })); + .convertVisitor( + await LivechatVisitors.findOneVisitorByPhone(phoneNumber, { 'source.type': OmnichannelSourceType.APP, 'source.id': appId }), + ); } protected async findDepartmentByIdOrName(value: string, appId: string): Promise { diff --git a/apps/meteor/app/apps/server/converters/visitors.js b/apps/meteor/app/apps/server/converters/visitors.js index 54c8ef42ffdef..c3d5dbad83f4a 100644 --- a/apps/meteor/app/apps/server/converters/visitors.js +++ b/apps/meteor/app/apps/server/converters/visitors.js @@ -10,7 +10,10 @@ export class AppVisitorsConverter { } async convertById(id, appId) { - const visitor = await LivechatVisitors.findOneEnabledByIdAndSource({ _id: id, source: { type: OmnichannelSourceType.APP, id: appId } }); + const visitor = await LivechatVisitors.findOneEnabledByIdAndSource({ + _id: id, + sourceFilter: { 'source.type': OmnichannelSourceType.APP, 'source.id': appId }, + }); return this.convertVisitor(visitor); } @@ -22,7 +25,10 @@ export class AppVisitorsConverter { } async convertByTokenAndSource(token, appId) { - const visitor = await LivechatVisitors.getVisitorByTokenAndSource({ token, source: { type: OmnichannelSourceType.APP, id: appId } }); + const visitor = await LivechatVisitors.getVisitorByTokenAndSource({ + token, + sourceFilter: { 'source.type': OmnichannelSourceType.APP, 'source.id': appId }, + }); return this.convertVisitor(visitor); } diff --git a/apps/meteor/app/livechat/imports/server/rest/sms.ts b/apps/meteor/app/livechat/imports/server/rest/sms.ts index dabb070674464..6546e3cd28a8e 100644 --- a/apps/meteor/app/livechat/imports/server/rest/sms.ts +++ b/apps/meteor/app/livechat/imports/server/rest/sms.ts @@ -62,7 +62,10 @@ const defineVisitor = async (smsNumber: string, serviceName: string, destination alias: serviceName, }; - const visitor = await LivechatVisitors.findOneVisitorByPhone(smsNumber, visitorSource); + const visitor = await LivechatVisitors.findOneVisitorByPhone(smsNumber, { + 'source.type': visitorSource.type, + 'source.alias': visitorSource.alias, + }); visitorSource.destination = destination; let data: { token: string; source: IOmnichannelSource; department?: string } = { token: visitor?.token || Random.id(), diff --git a/apps/meteor/app/livechat/imports/server/rest/upload.ts b/apps/meteor/app/livechat/imports/server/rest/upload.ts index 6bd2c9aa5ce0f..30bb686fe06b0 100644 --- a/apps/meteor/app/livechat/imports/server/rest/upload.ts +++ b/apps/meteor/app/livechat/imports/server/rest/upload.ts @@ -26,7 +26,10 @@ API.v1.addRoute('livechat/upload/:rid', { } const visitorToken = this.request.headers['x-visitor-token']; - const visitor = await LivechatVisitors.getVisitorByTokenAndSource({ token: visitorToken as string, source: { type: sourceType } }); + const visitor = await LivechatVisitors.getVisitorByTokenAndSource({ + token: visitorToken as string, + sourceFilter: { 'source.type': sourceType }, + }); if (!visitor) { return API.v1.unauthorized(); diff --git a/apps/meteor/app/livechat/server/api/lib/livechat.ts b/apps/meteor/app/livechat/server/api/lib/livechat.ts index 1844c9f4a168f..b5cee50971b74 100644 --- a/apps/meteor/app/livechat/server/api/lib/livechat.ts +++ b/apps/meteor/app/livechat/server/api/lib/livechat.ts @@ -68,7 +68,7 @@ export function findGuest(token: string, sourceType?: OmnichannelSourceType): Pr }; if (sourceType) { - return LivechatVisitors.getVisitorByTokenAndSource({ token, source: { type: sourceType } }, { projection }); + return LivechatVisitors.getVisitorByTokenAndSource({ token, sourceFilter: { 'source.type': sourceType } }, { projection }); } return LivechatVisitors.getVisitorByToken(token, { projection }); } diff --git a/apps/meteor/app/livechat/server/api/v1/message.ts b/apps/meteor/app/livechat/server/api/v1/message.ts index b1baefa5cb60a..776582de17c66 100644 --- a/apps/meteor/app/livechat/server/api/v1/message.ts +++ b/apps/meteor/app/livechat/server/api/v1/message.ts @@ -258,7 +258,10 @@ API.v1.addRoute( const visitorToken = this.bodyParams.visitor.token; const sourceType = isWidget(this.request.headers) ? OmnichannelSourceType.WIDGET : OmnichannelSourceType.API; - const visitor = await LivechatVisitors.getVisitorByTokenAndSource({ token: visitorToken, source: { type: sourceType } }, {}); + const visitor = await LivechatVisitors.getVisitorByTokenAndSource( + { token: visitorToken, sourceFilter: { 'source.type': sourceType } }, + {}, + ); let rid: string; if (visitor) { const extraQuery = await callbacks.run('livechat.applyRoomRestrictions', {}); diff --git a/apps/meteor/app/livechat/server/methods/sendMessageLivechat.ts b/apps/meteor/app/livechat/server/methods/sendMessageLivechat.ts index 7f3f748d950e6..badf4149081f6 100644 --- a/apps/meteor/app/livechat/server/methods/sendMessageLivechat.ts +++ b/apps/meteor/app/livechat/server/methods/sendMessageLivechat.ts @@ -43,13 +43,14 @@ export const sendMessageLivechat = async ({ ); const guest = await LivechatVisitors.getVisitorByTokenAndSource( - { token, source: { type: OmnichannelSourceType.API } }, + { token, sourceFilter: { 'source.type': { $in: [OmnichannelSourceType.API, OmnichannelSourceType.WIDGET] } } }, { projection: { name: 1, username: 1, department: 1, token: 1, + source: 1, }, }, ); diff --git a/apps/meteor/app/livechat/server/sendMessageBySMS.ts b/apps/meteor/app/livechat/server/sendMessageBySMS.ts index 9874165a7c66e..57013508673eb 100644 --- a/apps/meteor/app/livechat/server/sendMessageBySMS.ts +++ b/apps/meteor/app/livechat/server/sendMessageBySMS.ts @@ -58,7 +58,7 @@ callbacks.add( const visitorSource: IOmnichannelSource = { type: OmnichannelSourceType.SMS, alias: service }; const visitor = await LivechatVisitors.getVisitorByTokenAndSource( - { token: room.v.token, source: visitorSource }, + { token: room.v.token, sourceFilter: { 'source.type': visitorSource.type, 'source.alias': visitorSource.alias } }, { projection: { phone: 1, source: 1 } }, ); diff --git a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts index 3a9bd5e85f5b9..33c930b270d97 100644 --- a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts +++ b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts @@ -25,7 +25,10 @@ const language = settings.get('Language') || 'en'; const t = i18n.getFixedT(language); async function getGuestByEmail(email: string, name: string, inbox: string, department = ''): Promise { - const guest = await LivechatVisitors.findOneGuestByEmailAddress(email, { type: OmnichannelSourceType.EMAIL, id: inbox }); + const guest = await LivechatVisitors.findOneGuestByEmailAddress(email, { + 'source.type': OmnichannelSourceType.EMAIL, + 'source.id': inbox, + }); if (guest) { if (guest.department !== department) { diff --git a/apps/meteor/server/models/raw/LivechatVisitors.ts b/apps/meteor/server/models/raw/LivechatVisitors.ts index f1ef5d7beb1c1..9375d06161bc6 100644 --- a/apps/meteor/server/models/raw/LivechatVisitors.ts +++ b/apps/meteor/server/models/raw/LivechatVisitors.ts @@ -1,4 +1,4 @@ -import type { ILivechatVisitor, IOmnichannelSource, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; +import type { ILivechatVisitor, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; import type { FindPaginated, ILivechatVisitorsModel } from '@rocket.chat/model-typings'; import { Settings } from '@rocket.chat/models'; import { escapeRegExp } from '@rocket.chat/string-helpers'; @@ -30,7 +30,6 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL protected modelIndexes(): IndexDescription[] { return [ { key: { token: 1 } }, - { key: { 'token': 1, 'source.type': 1 } }, { key: { 'phone.phoneNumber': 1 }, sparse: true }, { key: { 'visitorEmails.address': 1 }, sparse: true }, { key: { name: 1 }, sparse: true }, @@ -42,21 +41,21 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL ]; } - findOneVisitorByPhone(phone: string, source?: IOmnichannelSource): Promise { + findOneVisitorByPhone(phone: string, sourceFilter?: Filter): Promise { const emptySourceFilter = { source: { $exists: false } }; const query = { 'phone.phoneNumber': phone, - ...(source ? { $or: [{ source }, emptySourceFilter] } : emptySourceFilter), + ...(sourceFilter ? { $or: [sourceFilter, emptySourceFilter] } : emptySourceFilter), }; return this.findOne(query); } - findOneGuestByEmailAddress(emailAddress: string, source?: IOmnichannelSource): Promise { + findOneGuestByEmailAddress(emailAddress: string, sourceFilter?: Filter): Promise { const emptySourceFilter = { source: { $exists: false } }; const query = { 'visitorEmails.address': String(emailAddress).toLowerCase(), - ...(source ? { $or: [{ source }, emptySourceFilter] } : emptySourceFilter), + ...(sourceFilter ? { $or: [sourceFilter, emptySourceFilter] } : emptySourceFilter), }; return this.findOne(query); @@ -75,14 +74,14 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL } findEnabledBySource( - source: IOmnichannelSource, + sourceFilter: Filter, query: Filter, options?: FindOptions, ): FindCursor { return this.find( { ...query, - $or: [{ source }, { source: { $exists: false } }], + $or: [sourceFilter, { source: { $exists: false } }], disabled: { $ne: true }, }, options, @@ -99,14 +98,14 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL } findOneEnabledByIdAndSource( - { _id, source }: { _id: string; source: IOmnichannelSource }, + { _id, sourceFilter }: { _id: string; sourceFilter: Filter }, options?: FindOptions, ): Promise { const emptySourceFilter = { source: { $exists: false } }; const query = { _id, disabled: { $ne: true }, - ...(source ? { $or: [{ source }, emptySourceFilter] } : emptySourceFilter), + ...(sourceFilter ? { $or: [sourceFilter, emptySourceFilter] } : emptySourceFilter), }; return this.findOne(query, options); @@ -130,13 +129,13 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL } getVisitorByTokenAndSource( - { token, source }: { token: string; source?: IOmnichannelSource }, + { token, sourceFilter }: { token: string; sourceFilter?: Filter }, options: FindOptions, ): Promise { const emptySourceFilter = { source: { $exists: false } }; const query = { token, - ...(source ? { $or: [{ source }, emptySourceFilter] } : emptySourceFilter), + ...(sourceFilter ? { $or: [sourceFilter, emptySourceFilter] } : emptySourceFilter), }; return this.findOne(query, options); diff --git a/packages/model-typings/src/models/ILivechatVisitorsModel.ts b/packages/model-typings/src/models/ILivechatVisitorsModel.ts index 31e83b96f697a..a2a0294907b9e 100644 --- a/packages/model-typings/src/models/ILivechatVisitorsModel.ts +++ b/packages/model-typings/src/models/ILivechatVisitorsModel.ts @@ -1,4 +1,4 @@ -import type { ILivechatVisitor, IOmnichannelSource } from '@rocket.chat/core-typings'; +import type { ILivechatVisitor } from '@rocket.chat/core-typings'; import type { AggregationCursor, FindCursor, @@ -17,7 +17,7 @@ export interface ILivechatVisitorsModel extends IBaseModel { findById(_id: string, options?: FindOptions): FindCursor; getVisitorByToken(token: string, options?: FindOptions): Promise; getVisitorByTokenAndSource( - { token, source }: { token: string; source?: IOmnichannelSource }, + { token, sourceFilter }: { token: string; sourceFilter?: Filter }, options?: FindOptions, ): Promise; getVisitorsBetweenDate({ start, end, department }: { start: Date; end: Date; department?: string }): FindCursor; @@ -49,9 +49,9 @@ export interface ILivechatVisitorsModel extends IBaseModel { updateLivechatDataByToken(token: string, key: string, value: unknown, overwrite: boolean): Promise; - findOneGuestByEmailAddress(emailAddress: string, source?: IOmnichannelSource): Promise; + findOneGuestByEmailAddress(emailAddress: string, sourceFilter?: Filter): Promise; - findOneVisitorByPhone(phone: string, source?: IOmnichannelSource): Promise; + findOneVisitorByPhone(phone: string, sourceFilter?: Filter): Promise; removeDepartmentById(_id: string): Promise; @@ -72,14 +72,14 @@ export interface ILivechatVisitorsModel extends IBaseModel { findOneEnabledById(_id: string, options?: FindOptions): Promise; findOneEnabledByIdAndSource( - { _id, source }: { _id: string; source: IOmnichannelSource }, + { _id, sourceFilter }: { _id: string; sourceFilter: Filter }, options?: FindOptions, ): Promise; disableById(_id: string): Promise; findEnabledBySource( - source: IOmnichannelSource, + source: Filter, query: Filter, options?: FindOptions, ): FindCursor; From 0161ec957fad184b9d4af85414ef4fe5c3916a0d Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Tue, 15 Oct 2024 01:17:53 -0300 Subject: [PATCH 14/23] Create changeset --- .changeset/fuzzy-pans-share.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/fuzzy-pans-share.md diff --git a/.changeset/fuzzy-pans-share.md b/.changeset/fuzzy-pans-share.md new file mode 100644 index 0000000000000..e689cb28df731 --- /dev/null +++ b/.changeset/fuzzy-pans-share.md @@ -0,0 +1,9 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/apps": minor +"@rocket.chat/core-typings": minor +"@rocket.chat/model-typings": minor +--- + +Adds a `source` field to livechat visitors, which stores the channel (eg API, widget, SMS, email-inbox, app) that's been used by the visitor to send messages. +Uses the new `source` field to assure each visitor is linked to a single source, so that each connection through a distinct channel creates a new visitor. From 0961db1f6aa2b85760af4f71ea7544b53d999a03 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Tue, 15 Oct 2024 09:24:54 -0300 Subject: [PATCH 15/23] improve methods names --- .../app/apps/server/bridges/livechat.ts | 9 +++++--- .../app/apps/server/converters/visitors.js | 8 ++++++- .../app/livechat/imports/server/rest/sms.ts | 2 +- .../app/livechat/server/api/lib/livechat.ts | 21 ++++++++++++++----- .../app/livechat/server/api/v1/message.ts | 2 +- .../EmailInbox/EmailInbox_Incoming.ts | 2 +- .../server/models/raw/LivechatVisitors.ts | 20 ++++++++++++++++-- .../src/converters/IAppVisitorsConverter.ts | 5 +++-- .../src/models/ILivechatVisitorsModel.ts | 10 ++++++--- 9 files changed, 60 insertions(+), 19 deletions(-) diff --git a/apps/meteor/app/apps/server/bridges/livechat.ts b/apps/meteor/app/apps/server/bridges/livechat.ts index 675309b4da1a9..8e9a2780820bb 100644 --- a/apps/meteor/app/apps/server/bridges/livechat.ts +++ b/apps/meteor/app/apps/server/bridges/livechat.ts @@ -310,7 +310,7 @@ export class AppLivechatBridge extends LivechatBridge { protected async findVisitorById(id: string, appId: string): Promise { this.orch.debugLog(`The App ${appId} is looking for livechat visitors.`); - return this.orch.getConverters()?.get('visitors').convertById(id, appId); + return this.orch.getConverters()?.get('visitors').convertByIdAndSource(id, appId); } protected async findVisitorByEmail(email: string, appId: string): Promise { @@ -320,7 +320,7 @@ export class AppLivechatBridge extends LivechatBridge { .getConverters() ?.get('visitors') .convertVisitor( - await LivechatVisitors.findOneGuestByEmailAddress(email, { 'source.type': OmnichannelSourceType.APP, 'source.id': appId }), + await LivechatVisitors.findOneGuestByEmailAddressAndSource(email, { 'source.type': OmnichannelSourceType.APP, 'source.id': appId }), ); } @@ -345,7 +345,10 @@ export class AppLivechatBridge extends LivechatBridge { .getConverters() ?.get('visitors') .convertVisitor( - await LivechatVisitors.findOneVisitorByPhone(phoneNumber, { 'source.type': OmnichannelSourceType.APP, 'source.id': appId }), + await LivechatVisitors.findOneVisitorByPhoneAndSource(phoneNumber, { + 'source.type': OmnichannelSourceType.APP, + 'source.id': appId, + }), ); } diff --git a/apps/meteor/app/apps/server/converters/visitors.js b/apps/meteor/app/apps/server/converters/visitors.js index c3d5dbad83f4a..c70d7905bdbd5 100644 --- a/apps/meteor/app/apps/server/converters/visitors.js +++ b/apps/meteor/app/apps/server/converters/visitors.js @@ -9,7 +9,13 @@ export class AppVisitorsConverter { this.orch = orch; } - async convertById(id, appId) { + async convertById(id) { + const visitor = await LivechatVisitors.findOneEnabledById(id); + + return this.convertVisitor(visitor); + } + + async convertByIdAndSource(id, appId) { const visitor = await LivechatVisitors.findOneEnabledByIdAndSource({ _id: id, sourceFilter: { 'source.type': OmnichannelSourceType.APP, 'source.id': appId }, diff --git a/apps/meteor/app/livechat/imports/server/rest/sms.ts b/apps/meteor/app/livechat/imports/server/rest/sms.ts index 6546e3cd28a8e..5bae04a956f7f 100644 --- a/apps/meteor/app/livechat/imports/server/rest/sms.ts +++ b/apps/meteor/app/livechat/imports/server/rest/sms.ts @@ -62,7 +62,7 @@ const defineVisitor = async (smsNumber: string, serviceName: string, destination alias: serviceName, }; - const visitor = await LivechatVisitors.findOneVisitorByPhone(smsNumber, { + const visitor = await LivechatVisitors.findOneVisitorByPhoneAndSource(smsNumber, { 'source.type': visitorSource.type, 'source.alias': visitorSource.alias, }); diff --git a/apps/meteor/app/livechat/server/api/lib/livechat.ts b/apps/meteor/app/livechat/server/api/lib/livechat.ts index b5cee50971b74..4c1bda8cd89ab 100644 --- a/apps/meteor/app/livechat/server/api/lib/livechat.ts +++ b/apps/meteor/app/livechat/server/api/lib/livechat.ts @@ -55,7 +55,7 @@ async function findDepartments( })); } -export function findGuest(token: string, sourceType?: OmnichannelSourceType): Promise { +export function findGuest(token: string): Promise { const projection = { name: 1, username: 1, @@ -64,15 +64,26 @@ export function findGuest(token: string, sourceType?: OmnichannelSourceType): Pr department: 1, activity: 1, contactId: 1, - source: 1, }; - if (sourceType) { - return LivechatVisitors.getVisitorByTokenAndSource({ token, sourceFilter: { 'source.type': sourceType } }, { projection }); - } return LivechatVisitors.getVisitorByToken(token, { projection }); } +export function findGuestBySource(token: string, sourceType: OmnichannelSourceType): Promise { + const projection = { + name: 1, + username: 1, + token: 1, + visitorEmails: 1, + department: 1, + activity: 1, + contactId: 1, + source: 1, + }; + + return LivechatVisitors.getVisitorByTokenAndSource({ token, sourceFilter: { 'source.type': sourceType } }, { projection }); +} + export function findGuestWithoutActivity(token: string): Promise { return LivechatVisitors.getVisitorByToken(token, { projection: { name: 1, username: 1, token: 1, visitorEmails: 1, department: 1 } }); } diff --git a/apps/meteor/app/livechat/server/api/v1/message.ts b/apps/meteor/app/livechat/server/api/v1/message.ts index 776582de17c66..d90a88ce81153 100644 --- a/apps/meteor/app/livechat/server/api/v1/message.ts +++ b/apps/meteor/app/livechat/server/api/v1/message.ts @@ -29,7 +29,7 @@ API.v1.addRoute( const { token, rid, agent, msg } = this.bodyParams; const sourceType = isWidget(this.request.headers) ? OmnichannelSourceType.WIDGET : OmnichannelSourceType.API; - const guest = await findGuest(token, sourceType); + const guest = await findGuestBySource(token, sourceType); if (!guest) { throw new Error('invalid-token'); } diff --git a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts index 33c930b270d97..5247c8f759bad 100644 --- a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts +++ b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts @@ -25,7 +25,7 @@ const language = settings.get('Language') || 'en'; const t = i18n.getFixedT(language); async function getGuestByEmail(email: string, name: string, inbox: string, department = ''): Promise { - const guest = await LivechatVisitors.findOneGuestByEmailAddress(email, { + const guest = await LivechatVisitors.findOneGuestByEmailAddressAndSource(email, { 'source.type': OmnichannelSourceType.EMAIL, 'source.id': inbox, }); diff --git a/apps/meteor/server/models/raw/LivechatVisitors.ts b/apps/meteor/server/models/raw/LivechatVisitors.ts index 9375d06161bc6..2121e4899832d 100644 --- a/apps/meteor/server/models/raw/LivechatVisitors.ts +++ b/apps/meteor/server/models/raw/LivechatVisitors.ts @@ -41,7 +41,15 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL ]; } - findOneVisitorByPhone(phone: string, sourceFilter?: Filter): Promise { + findOneVisitorByPhone(phone: string): Promise { + const query = { + 'phone.phoneNumber': phone, + }; + + return this.findOne(query); + } + + findOneVisitorByPhoneAndSource(phone: string, sourceFilter: Filter): Promise { const emptySourceFilter = { source: { $exists: false } }; const query = { 'phone.phoneNumber': phone, @@ -51,7 +59,15 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL return this.findOne(query); } - findOneGuestByEmailAddress(emailAddress: string, sourceFilter?: Filter): Promise { + findOneGuestByEmailAddress(emailAddress: string): Promise { + const query = { + 'visitorEmails.address': String(emailAddress).toLowerCase(), + }; + + return this.findOne(query); + } + + findOneGuestByEmailAddressAndSource(emailAddress: string, sourceFilter: Filter): Promise { const emptySourceFilter = { source: { $exists: false } }; const query = { 'visitorEmails.address': String(emailAddress).toLowerCase(), diff --git a/packages/apps/src/converters/IAppVisitorsConverter.ts b/packages/apps/src/converters/IAppVisitorsConverter.ts index 8feb9925c4494..5d359888155f0 100644 --- a/packages/apps/src/converters/IAppVisitorsConverter.ts +++ b/packages/apps/src/converters/IAppVisitorsConverter.ts @@ -3,9 +3,10 @@ import type { ILivechatVisitor } from '@rocket.chat/core-typings'; import type { IAppsVisitor } from '../AppsEngine'; export interface IAppVisitorsConverter { - convertById(visitorId: ILivechatVisitor['_id'], appId?: string): Promise; + convertById(visitorId: ILivechatVisitor['_id']): Promise; + convertByIdAndSource(visitorId: ILivechatVisitor['_id'], appId: string): Promise; convertByToken(token: string): Promise; - convertByTokenAndSource(token: string, appId?: string): Promise; + convertByTokenAndSource(token: string, appId: string): Promise; convertVisitor(visitor: undefined | null): Promise; convertVisitor(visitor: ILivechatVisitor): Promise; convertVisitor(visitor: ILivechatVisitor | undefined | null): Promise; diff --git a/packages/model-typings/src/models/ILivechatVisitorsModel.ts b/packages/model-typings/src/models/ILivechatVisitorsModel.ts index a2a0294907b9e..56fe23201219b 100644 --- a/packages/model-typings/src/models/ILivechatVisitorsModel.ts +++ b/packages/model-typings/src/models/ILivechatVisitorsModel.ts @@ -49,9 +49,13 @@ export interface ILivechatVisitorsModel extends IBaseModel { updateLivechatDataByToken(token: string, key: string, value: unknown, overwrite: boolean): Promise; - findOneGuestByEmailAddress(emailAddress: string, sourceFilter?: Filter): Promise; + findOneGuestByEmailAddress(emailAddress: string): Promise; - findOneVisitorByPhone(phone: string, sourceFilter?: Filter): Promise; + findOneGuestByEmailAddressAndSource(emailAddress: string, sourceFilter: Filter): Promise; + + findOneVisitorByPhone(phone: string): Promise; + + findOneVisitorByPhoneAndSource(phone: string, sourceFilter: Filter): Promise; removeDepartmentById(_id: string): Promise; @@ -79,7 +83,7 @@ export interface ILivechatVisitorsModel extends IBaseModel { disableById(_id: string): Promise; findEnabledBySource( - source: Filter, + sourceFilter: Filter, query: Filter, options?: FindOptions, ): FindCursor; From 65b2147a09a80c6fd5254153169e81335492cb28 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Tue, 15 Oct 2024 09:25:10 -0300 Subject: [PATCH 16/23] apply changes requested to tests --- .../tests/end-to-end/api/livechat/00-rooms.ts | 47 ++++++++++--------- .../api/livechat/06-integrations.ts | 7 ++- .../end-to-end/api/livechat/11-livechat.ts | 11 ++++- 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts index f8a198fbdeb35..e67e30d46f09c 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts @@ -13,7 +13,7 @@ import type { } from '@rocket.chat/core-typings'; import { LivechatPriorityWeight } from '@rocket.chat/core-typings'; import { expect } from 'chai'; -import { after, before, describe, it } from 'mocha'; +import { after, afterEach, before, describe, it } from 'mocha'; import type { Response } from 'supertest'; import type { SuccessResult } from '../../../../app/api/server/definition'; @@ -1150,6 +1150,19 @@ describe('LIVECHAT - rooms', () => { }); describe('livechat/upload/:rid', () => { + let visitor: ILivechatVisitor | undefined; + + afterEach(() => { + if (visitor?.token) { + return deleteVisitor(visitor.token); + } + }); + + after(async () => { + await updateSetting('FileUpload_Enabled', true); + await updateSetting('Livechat_fileupload_enabled', true); + }); + it('should throw an error if x-visitor-token header is not present', async () => { await request .post(api('livechat/upload/test')) @@ -1170,7 +1183,7 @@ describe('LIVECHAT - rooms', () => { }); it('should throw unauthorized if visitor with token exists but room is invalid', async () => { - const visitor = await createVisitor(); + visitor = await createVisitor(); await request .post(api('livechat/upload/test')) .set(credentials) @@ -1178,11 +1191,10 @@ describe('LIVECHAT - rooms', () => { .attach('file', fs.createReadStream(path.join(__dirname, '../../../data/livechat/sample.png'))) .expect('Content-Type', 'application/json') .expect(403); - await deleteVisitor(visitor.token); }); it('should throw an error if the file is not attached', async () => { - const visitor = await createVisitor(); + visitor = await createVisitor(); const room = await createLivechatRoom(visitor.token); await request .post(api(`livechat/upload/${room._id}`)) @@ -1190,12 +1202,11 @@ describe('LIVECHAT - rooms', () => { .set('x-visitor-token', visitor.token) .expect('Content-Type', 'application/json') .expect(400); - await deleteVisitor(visitor.token); }); it('should throw and error if file uploads are enabled but livechat file uploads are disabled', async () => { await updateSetting('Livechat_fileupload_enabled', false); - const visitor = await createVisitor(); + visitor = await createVisitor(); const room = await createLivechatRoom(visitor.token); await request .post(api(`livechat/upload/${room._id}`)) @@ -1205,12 +1216,11 @@ describe('LIVECHAT - rooms', () => { .expect('Content-Type', 'application/json') .expect(400); await updateSetting('Livechat_fileupload_enabled', true); - await deleteVisitor(visitor.token); }); it('should throw and error if livechat file uploads are enabled but file uploads are disabled', async () => { await updateSetting('FileUpload_Enabled', false); - const visitor = await createVisitor(); + visitor = await createVisitor(); const room = await createLivechatRoom(visitor.token); await request .post(api(`livechat/upload/${room._id}`)) @@ -1220,13 +1230,12 @@ describe('LIVECHAT - rooms', () => { .expect('Content-Type', 'application/json') .expect(400); await updateSetting('FileUpload_Enabled', true); - await deleteVisitor(visitor.token); }); it('should throw and error if both file uploads are disabled', async () => { await updateSetting('Livechat_fileupload_enabled', false); await updateSetting('FileUpload_Enabled', false); - const visitor = await createVisitor(); + visitor = await createVisitor(); const room = await createLivechatRoom(visitor.token); await request .post(api(`livechat/upload/${room._id}`)) @@ -1237,14 +1246,12 @@ describe('LIVECHAT - rooms', () => { .expect(400); await updateSetting('FileUpload_Enabled', true); await updateSetting('Livechat_fileupload_enabled', true); - - await deleteVisitor(visitor.token); }); it('should upload an image on the room if all params are valid', async () => { await updateSetting('FileUpload_Enabled', true); await updateSetting('Livechat_fileupload_enabled', true); - const visitor = await createVisitor(); + visitor = await createVisitor(); const room = await createLivechatRoom(visitor.token); await request .post(api(`livechat/upload/${room._id}`)) @@ -1253,13 +1260,12 @@ describe('LIVECHAT - rooms', () => { .attach('file', fs.createReadStream(path.join(__dirname, '../../../data/livechat/sample.png'))) .expect('Content-Type', 'application/json') .expect(200); - await deleteVisitor(visitor.token); }); it("should set visitor's source as API after uploading a file", async () => { await updateSetting('FileUpload_Enabled', true); await updateSetting('Livechat_fileupload_enabled', true); - const visitor = await createVisitor(); + visitor = await createVisitor(); const room = await createLivechatRoom(visitor.token); await request .post(api(`livechat/upload/${room._id}`)) @@ -1278,12 +1284,10 @@ describe('LIVECHAT - rooms', () => { expect(body).to.have.property('visitor').and.to.be.an('object'); expect(body.visitor).to.have.property('source').and.to.be.an('object'); expect(body.visitor.source).to.have.property('type', 'api'); - - await deleteVisitor(visitor.token); }); it('should allow visitor to download file', async () => { - const visitor = await createVisitor(); + visitor = await createVisitor(); const room = await createLivechatRoom(visitor.token); const { body } = await request @@ -1298,12 +1302,11 @@ describe('LIVECHAT - rooms', () => { } = body; const imageUrl = `/file-upload/${_id}/${name}`; await request.get(imageUrl).query({ rc_token: visitor.token, rc_room_type: 'l', rc_rid: room._id }).expect(200); - await deleteVisitor(visitor.token); await closeOmnichannelRoom(room._id); }); it('should allow visitor to download file even after room is closed', async () => { - const visitor = await createVisitor(); + visitor = await createVisitor(); const room = await createLivechatRoom(visitor.token); const { body } = await request .post(api(`livechat/upload/${room._id}`)) @@ -1318,11 +1321,10 @@ describe('LIVECHAT - rooms', () => { } = body; const imageUrl = `/file-upload/${_id}/${name}`; await request.get(imageUrl).query({ rc_token: visitor.token, rc_room_type: 'l', rc_rid: room._id }).expect(200); - await deleteVisitor(visitor.token); }); it('should not allow visitor to download a file from a room he didnt create', async () => { - const visitor = await createVisitor(); + visitor = await createVisitor(); const visitor2 = await createVisitor(); const room = await createLivechatRoom(visitor.token); const { body } = await request @@ -1339,7 +1341,6 @@ describe('LIVECHAT - rooms', () => { } = body; const imageUrl = `/file-upload/${_id}/${name}`; await request.get(imageUrl).query({ rc_token: visitor2.token, rc_room_type: 'l', rc_rid: room._id }).expect(403); - await deleteVisitor(visitor.token); await deleteVisitor(visitor2.token); }); }); diff --git a/apps/meteor/tests/end-to-end/api/livechat/06-integrations.ts b/apps/meteor/tests/end-to-end/api/livechat/06-integrations.ts index 957152976f14b..de382ac9fa747 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/06-integrations.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/06-integrations.ts @@ -1,6 +1,6 @@ import type { ISetting } from '@rocket.chat/core-typings'; import { expect } from 'chai'; -import { before, describe, it } from 'mocha'; +import { after, before, describe, it } from 'mocha'; import type { Response } from 'supertest'; import { getCredentials, api, request, credentials } from '../../../data/api-data'; @@ -54,6 +54,11 @@ describe('LIVECHAT - Integrations', () => { await updateSetting('SMS_Service', ''); }); + after(async () => { + await updateSetting('SMS_Default_Omnichannel_Department', ''); + await updateSetting('SMS_Service', 'twilio'); + }); + describe('POST livechat/sms-incoming/:service', () => { it('should throw an error if SMS is disabled', async () => { await updateSetting('SMS_Enabled', false); diff --git a/apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts b/apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts index b3952be5043e0..9d5a44ffa6312 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts @@ -1,5 +1,6 @@ +import type { ILivechatVisitor } from '@rocket.chat/core-typings'; import { expect } from 'chai'; -import { after, before, describe, it } from 'mocha'; +import { after, afterEach, before, describe, it } from 'mocha'; import { sleep } from '../../../../lib/utils/sleep'; import { getCredentials, api, request, credentials } from '../../../data/api-data'; @@ -516,6 +517,14 @@ describe('LIVECHAT - Utils', () => { }); describe('livechat/message', () => { + let visitor: ILivechatVisitor | undefined; + + afterEach(() => { + if (visitor?.token) { + return deleteVisitor(visitor.token); + } + }); + it('should fail if no token', async () => { await request.post(api('livechat/message')).set(credentials).send({}).expect(400); }); From a3dd9754e1260afc13d7bced1b8ec8e97acf209d Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Tue, 15 Oct 2024 10:12:05 -0300 Subject: [PATCH 17/23] Fix typecheck --- apps/meteor/app/livechat/server/api/v1/message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/app/livechat/server/api/v1/message.ts b/apps/meteor/app/livechat/server/api/v1/message.ts index d90a88ce81153..578cdad8cfc5b 100644 --- a/apps/meteor/app/livechat/server/api/v1/message.ts +++ b/apps/meteor/app/livechat/server/api/v1/message.ts @@ -19,7 +19,7 @@ import { loadMessageHistory } from '../../../../lib/server/functions/loadMessage import { settings } from '../../../../settings/server'; import { normalizeMessageFileUpload } from '../../../../utils/server/functions/normalizeMessageFileUpload'; import { Livechat as LivechatTyped } from '../../lib/LivechatTyped'; -import { findGuest, findRoom, normalizeHttpHeaderData } from '../lib/livechat'; +import { findGuest, findGuestBysource, findRoom, normalizeHttpHeaderData } from '../lib/livechat'; API.v1.addRoute( 'livechat/message', From 26b7464f20901b9593bc300142ce6c1de17a701b Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Tue, 15 Oct 2024 10:50:16 -0300 Subject: [PATCH 18/23] Update message.ts --- apps/meteor/app/livechat/server/api/v1/message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/app/livechat/server/api/v1/message.ts b/apps/meteor/app/livechat/server/api/v1/message.ts index 578cdad8cfc5b..bde332af5db42 100644 --- a/apps/meteor/app/livechat/server/api/v1/message.ts +++ b/apps/meteor/app/livechat/server/api/v1/message.ts @@ -19,7 +19,7 @@ import { loadMessageHistory } from '../../../../lib/server/functions/loadMessage import { settings } from '../../../../settings/server'; import { normalizeMessageFileUpload } from '../../../../utils/server/functions/normalizeMessageFileUpload'; import { Livechat as LivechatTyped } from '../../lib/LivechatTyped'; -import { findGuest, findGuestBysource, findRoom, normalizeHttpHeaderData } from '../lib/livechat'; +import { findGuest, findGuestBySource, findRoom, normalizeHttpHeaderData } from '../lib/livechat'; API.v1.addRoute( 'livechat/message', From c98750db9b9cd42e4fa1cc189070f8e3dc19c8aa Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Tue, 15 Oct 2024 11:17:23 -0300 Subject: [PATCH 19/23] Apply more changes requested --- .../api/livechat/06-integrations.ts | 5 +++++ .../end-to-end/api/livechat/11-livechat.ts | 22 ++++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/apps/meteor/tests/end-to-end/api/livechat/06-integrations.ts b/apps/meteor/tests/end-to-end/api/livechat/06-integrations.ts index de382ac9fa747..54b888645a73c 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/06-integrations.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/06-integrations.ts @@ -4,6 +4,7 @@ import { after, before, describe, it } from 'mocha'; import type { Response } from 'supertest'; import { getCredentials, api, request, credentials } from '../../../data/api-data'; +import { deleteVisitor } from '../../../data/livechat/rooms'; import { updatePermission, updateSetting } from '../../../data/permissions.helper'; describe('LIVECHAT - Integrations', () => { @@ -49,6 +50,8 @@ describe('LIVECHAT - Integrations', () => { }); describe('Incoming SMS', () => { + const visitorTokens: string[] = []; + before(async () => { await updateSetting('SMS_Enabled', true); await updateSetting('SMS_Service', ''); @@ -57,6 +60,7 @@ describe('LIVECHAT - Integrations', () => { after(async () => { await updateSetting('SMS_Default_Omnichannel_Department', ''); await updateSetting('SMS_Service', 'twilio'); + return Promise.all(visitorTokens.map((token) => deleteVisitor(token))); }); describe('POST livechat/sms-incoming/:service', () => { @@ -132,6 +136,7 @@ describe('LIVECHAT - Integrations', () => { expect(createVisitorResponse).to.have.property('visitor').and.to.be.an('object'); expect(createVisitorResponse.visitor).to.have.property('_id'); const visitorId = createVisitorResponse.visitor._id; + visitorTokens.push(createVisitorResponse.visitor.token); await request .post(api('livechat/sms-incoming/twilio')) diff --git a/apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts b/apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts index 9d5a44ffa6312..b5f237832f598 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/11-livechat.ts @@ -1,6 +1,5 @@ -import type { ILivechatVisitor } from '@rocket.chat/core-typings'; import { expect } from 'chai'; -import { after, afterEach, before, describe, it } from 'mocha'; +import { after, before, describe, it } from 'mocha'; import { sleep } from '../../../../lib/utils/sleep'; import { getCredentials, api, request, credentials } from '../../../data/api-data'; @@ -517,13 +516,9 @@ describe('LIVECHAT - Utils', () => { }); describe('livechat/message', () => { - let visitor: ILivechatVisitor | undefined; + const visitorTokens: string[] = []; - afterEach(() => { - if (visitor?.token) { - return deleteVisitor(visitor.token); - } - }); + after(() => Promise.all(visitorTokens.map((token) => deleteVisitor(token)))); it('should fail if no token', async () => { await request.post(api('livechat/message')).set(credentials).send({}).expect(400); @@ -539,22 +534,29 @@ describe('LIVECHAT - Utils', () => { }); it('should fail if rid is invalid', async () => { const visitor = await createVisitor(); + visitorTokens.push(visitor.token); await request.post(api('livechat/message')).set(credentials).send({ token: visitor.token, rid: 'test', msg: 'test' }).expect(400); }); it('should fail if rid belongs to another visitor', async () => { const visitor = await createVisitor(); const visitor2 = await createVisitor(); + visitorTokens.push(visitor.token, visitor2.token); + const room = await createLivechatRoom(visitor2.token); await request.post(api('livechat/message')).set(credentials).send({ token: visitor.token, rid: room._id, msg: 'test' }).expect(400); }); it('should fail if room is closed', async () => { const visitor = await createVisitor(); + visitorTokens.push(visitor.token); + const room = await createLivechatRoom(visitor.token); await closeOmnichannelRoom(room._id); await request.post(api('livechat/message')).set(credentials).send({ token: visitor.token, rid: room._id, msg: 'test' }).expect(400); }); it('should fail if message is greater than Livechat_enable_message_character_limit setting', async () => { const visitor = await createVisitor(); + visitorTokens.push(visitor.token); + const room = await createLivechatRoom(visitor.token); await updateSetting('Livechat_enable_message_character_limit', true); await updateSetting('Livechat_message_character_limit', 1); @@ -564,11 +566,15 @@ describe('LIVECHAT - Utils', () => { }); it('should send a message', async () => { const visitor = await createVisitor(); + visitorTokens.push(visitor.token); + const room = await createLivechatRoom(visitor.token); await request.post(api('livechat/message')).set(credentials).send({ token: visitor.token, rid: room._id, msg: 'test' }).expect(200); }); it("should set visitor's source as API after sending a message", async () => { const visitor = await createVisitor(); + visitorTokens.push(visitor.token); + const room = await createLivechatRoom(visitor.token); await request.post(api('livechat/message')).set(credentials).send({ token: visitor.token, rid: room._id, msg: 'test' }).expect(200); From 45604bfec92f465b8e80080fed55d0053581819f Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Tue, 15 Oct 2024 11:17:41 -0300 Subject: [PATCH 20/23] Revert changes to findGuest function --- .../app/livechat/server/api/lib/livechat.ts | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/meteor/app/livechat/server/api/lib/livechat.ts b/apps/meteor/app/livechat/server/api/lib/livechat.ts index 4c1bda8cd89ab..03aca9da02f30 100644 --- a/apps/meteor/app/livechat/server/api/lib/livechat.ts +++ b/apps/meteor/app/livechat/server/api/lib/livechat.ts @@ -56,17 +56,17 @@ async function findDepartments( } export function findGuest(token: string): Promise { - const projection = { - name: 1, - username: 1, - token: 1, - visitorEmails: 1, - department: 1, - activity: 1, - contactId: 1, - }; - - return LivechatVisitors.getVisitorByToken(token, { projection }); + return LivechatVisitors.getVisitorByToken(token, { + projection: { + name: 1, + username: 1, + token: 1, + visitorEmails: 1, + department: 1, + activity: 1, + contactId: 1, + }, + }); } export function findGuestBySource(token: string, sourceType: OmnichannelSourceType): Promise { From e0695686761fb428e74b551d67c82d7449e3de2d Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Tue, 15 Oct 2024 15:48:05 -0300 Subject: [PATCH 21/23] apply changes requested --- .../app/livechat/imports/server/rest/sms.ts | 12 ++++++--- .../app/livechat/server/api/lib/livechat.ts | 12 +++++++-- .../EmailInbox/EmailInbox_Incoming.ts | 12 ++++++--- .../server/models/raw/LivechatVisitors.ts | 25 +++++++++++-------- .../src/models/ILivechatVisitorsModel.ts | 12 +++++++-- 5 files changed, 51 insertions(+), 22 deletions(-) diff --git a/apps/meteor/app/livechat/imports/server/rest/sms.ts b/apps/meteor/app/livechat/imports/server/rest/sms.ts index 5bae04a956f7f..3004423c64ba9 100644 --- a/apps/meteor/app/livechat/imports/server/rest/sms.ts +++ b/apps/meteor/app/livechat/imports/server/rest/sms.ts @@ -62,10 +62,14 @@ const defineVisitor = async (smsNumber: string, serviceName: string, destination alias: serviceName, }; - const visitor = await LivechatVisitors.findOneVisitorByPhoneAndSource(smsNumber, { - 'source.type': visitorSource.type, - 'source.alias': visitorSource.alias, - }); + const visitor = await LivechatVisitors.findOneVisitorByPhoneAndSource( + smsNumber, + { + 'source.type': visitorSource.type, + 'source.alias': visitorSource.alias, + }, + { projection: { token: 1 } }, + ); visitorSource.destination = destination; let data: { token: string; source: IOmnichannelSource; department?: string } = { token: visitor?.token || Random.id(), diff --git a/apps/meteor/app/livechat/server/api/lib/livechat.ts b/apps/meteor/app/livechat/server/api/lib/livechat.ts index 03aca9da02f30..f455693dc9201 100644 --- a/apps/meteor/app/livechat/server/api/lib/livechat.ts +++ b/apps/meteor/app/livechat/server/api/lib/livechat.ts @@ -55,7 +55,9 @@ async function findDepartments( })); } -export function findGuest(token: string): Promise { +export function findGuest( + token: string, +): Promise | null> { return LivechatVisitors.getVisitorByToken(token, { projection: { name: 1, @@ -69,7 +71,13 @@ export function findGuest(token: string): Promise { }); } -export function findGuestBySource(token: string, sourceType: OmnichannelSourceType): Promise { +export function findGuestBySource( + token: string, + sourceType: OmnichannelSourceType, +): Promise | null> { const projection = { name: 1, username: 1, diff --git a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts index 5247c8f759bad..907f84998d062 100644 --- a/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts +++ b/apps/meteor/server/features/EmailInbox/EmailInbox_Incoming.ts @@ -25,10 +25,14 @@ const language = settings.get('Language') || 'en'; const t = i18n.getFixedT(language); async function getGuestByEmail(email: string, name: string, inbox: string, department = ''): Promise { - const guest = await LivechatVisitors.findOneGuestByEmailAddressAndSource(email, { - 'source.type': OmnichannelSourceType.EMAIL, - 'source.id': inbox, - }); + const guest = await LivechatVisitors.findOneGuestByEmailAddressAndSource( + email, + { + 'source.type': OmnichannelSourceType.EMAIL, + 'source.id': inbox, + }, + { projection: { department: 1, token: 1, source: 1 } }, + ); if (guest) { if (guest.department !== department) { diff --git a/apps/meteor/server/models/raw/LivechatVisitors.ts b/apps/meteor/server/models/raw/LivechatVisitors.ts index 2121e4899832d..1c371449fd93a 100644 --- a/apps/meteor/server/models/raw/LivechatVisitors.ts +++ b/apps/meteor/server/models/raw/LivechatVisitors.ts @@ -22,6 +22,7 @@ import { ObjectId } from 'mongodb'; import { notifyOnSettingChanged } from '../../../app/lib/server/lib/notifyListener'; import { BaseRaw } from './BaseRaw'; +const emptySourceFilter = { source: { $exists: false } }; export class LivechatVisitorsRaw extends BaseRaw implements ILivechatVisitorsModel { constructor(db: Db, trash?: Collection>) { super(db, 'livechat_visitor', trash); @@ -49,14 +50,17 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL return this.findOne(query); } - findOneVisitorByPhoneAndSource(phone: string, sourceFilter: Filter): Promise { - const emptySourceFilter = { source: { $exists: false } }; + findOneVisitorByPhoneAndSource( + phone: string, + sourceFilter: Filter, + options?: FindOptions, + ): Promise { const query = { 'phone.phoneNumber': phone, ...(sourceFilter ? { $or: [sourceFilter, emptySourceFilter] } : emptySourceFilter), }; - return this.findOne(query); + return this.findOne(query, options); } findOneGuestByEmailAddress(emailAddress: string): Promise { @@ -67,14 +71,17 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL return this.findOne(query); } - findOneGuestByEmailAddressAndSource(emailAddress: string, sourceFilter: Filter): Promise { - const emptySourceFilter = { source: { $exists: false } }; + findOneGuestByEmailAddressAndSource( + emailAddress: string, + sourceFilter: Filter, + options?: FindOptions, + ): Promise { const query = { - 'visitorEmails.address': String(emailAddress).toLowerCase(), + 'visitorEmails.address': emailAddress.toLowerCase(), ...(sourceFilter ? { $or: [sourceFilter, emptySourceFilter] } : emptySourceFilter), }; - return this.findOne(query); + return this.findOne(query, options); } /** @@ -97,7 +104,7 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL return this.find( { ...query, - $or: [sourceFilter, { source: { $exists: false } }], + $or: [sourceFilter, emptySourceFilter], disabled: { $ne: true }, }, options, @@ -117,7 +124,6 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL { _id, sourceFilter }: { _id: string; sourceFilter: Filter }, options?: FindOptions, ): Promise { - const emptySourceFilter = { source: { $exists: false } }; const query = { _id, disabled: { $ne: true }, @@ -148,7 +154,6 @@ export class LivechatVisitorsRaw extends BaseRaw implements IL { token, sourceFilter }: { token: string; sourceFilter?: Filter }, options: FindOptions, ): Promise { - const emptySourceFilter = { source: { $exists: false } }; const query = { token, ...(sourceFilter ? { $or: [sourceFilter, emptySourceFilter] } : emptySourceFilter), diff --git a/packages/model-typings/src/models/ILivechatVisitorsModel.ts b/packages/model-typings/src/models/ILivechatVisitorsModel.ts index 56fe23201219b..7b47a1b3f0cc2 100644 --- a/packages/model-typings/src/models/ILivechatVisitorsModel.ts +++ b/packages/model-typings/src/models/ILivechatVisitorsModel.ts @@ -51,11 +51,19 @@ export interface ILivechatVisitorsModel extends IBaseModel { findOneGuestByEmailAddress(emailAddress: string): Promise; - findOneGuestByEmailAddressAndSource(emailAddress: string, sourceFilter: Filter): Promise; + findOneGuestByEmailAddressAndSource( + emailAddress: string, + sourceFilter: Filter, + options?: FindOptions, + ): Promise; findOneVisitorByPhone(phone: string): Promise; - findOneVisitorByPhoneAndSource(phone: string, sourceFilter: Filter): Promise; + findOneVisitorByPhoneAndSource( + phone: string, + sourceFilter: Filter, + options?: FindOptions, + ): Promise; removeDepartmentById(_id: string): Promise; From a5696b24212a638f30bfe9d395ac88c37fe28e91 Mon Sep 17 00:00:00 2001 From: matheusbsilva137 Date: Tue, 15 Oct 2024 17:10:10 -0300 Subject: [PATCH 22/23] Undo typing improvements --- apps/meteor/app/livechat/server/api/lib/livechat.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/apps/meteor/app/livechat/server/api/lib/livechat.ts b/apps/meteor/app/livechat/server/api/lib/livechat.ts index f455693dc9201..03aca9da02f30 100644 --- a/apps/meteor/app/livechat/server/api/lib/livechat.ts +++ b/apps/meteor/app/livechat/server/api/lib/livechat.ts @@ -55,9 +55,7 @@ async function findDepartments( })); } -export function findGuest( - token: string, -): Promise | null> { +export function findGuest(token: string): Promise { return LivechatVisitors.getVisitorByToken(token, { projection: { name: 1, @@ -71,13 +69,7 @@ export function findGuest( }); } -export function findGuestBySource( - token: string, - sourceType: OmnichannelSourceType, -): Promise | null> { +export function findGuestBySource(token: string, sourceType: OmnichannelSourceType): Promise { const projection = { name: 1, username: 1, From bff9207257d488230f4f4808aeb666990e945489 Mon Sep 17 00:00:00 2001 From: Pierre Date: Thu, 24 Oct 2024 14:17:49 -0300 Subject: [PATCH 23/23] do not restrict visitors on uikit block --- apps/meteor/ee/server/apps/communication/uikit.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/meteor/ee/server/apps/communication/uikit.ts b/apps/meteor/ee/server/apps/communication/uikit.ts index 87bb37965d075..0392076704d72 100644 --- a/apps/meteor/ee/server/apps/communication/uikit.ts +++ b/apps/meteor/ee/server/apps/communication/uikit.ts @@ -61,10 +61,9 @@ router.use(authenticationMiddleware({ rejectUnauthorized: false })); router.use(async (req: Request, res, next) => { const { 'x-visitor-token': visitorToken } = req.headers; - const { id: appId } = req.params; if (visitorToken) { - req.body.visitor = await Apps.getConverters()?.get('visitors').convertByTokenAndSource(visitorToken, appId); + req.body.visitor = await Apps.getConverters()?.get('visitors').convertByToken(visitorToken); } if (!req.user && !req.body.visitor) {