diff --git a/apps/meteor/app/apps/server/bridges/livechat.ts b/apps/meteor/app/apps/server/bridges/livechat.ts index 0ace08bb8446..5b6c76257667 100644 --- a/apps/meteor/app/apps/server/bridges/livechat.ts +++ b/apps/meteor/app/apps/server/bridges/livechat.ts @@ -74,7 +74,8 @@ export class AppLivechatBridge extends LivechatBridge { message: await this.orch.getConverters()?.get('messages').convertAppMessage(message), }; - await Livechat.updateMessage(data); + // @ts-expect-error IVisitor vs ILivechatVisitor :( + await LivechatTyped.updateMessage(data); } protected async createRoom(visitor: IVisitor, agent: IUser, appId: string, extraParams?: IExtraRoomParams): Promise { @@ -208,7 +209,7 @@ export class AppLivechatBridge extends LivechatBridge { userId = transferredTo._id; } - return Livechat.transfer( + return LivechatTyped.transfer( await this.orch.getConverters()?.get('rooms').convertAppRoom(currentRoom), this.orch.getConverters()?.get('visitors').convertAppVisitor(visitor), { userId, departmentId, transferredBy, transferredTo }, diff --git a/apps/meteor/app/livechat/server/api/lib/livechat.ts b/apps/meteor/app/livechat/server/api/lib/livechat.ts index 7bb608090557..2b72065345d6 100644 --- a/apps/meteor/app/livechat/server/api/lib/livechat.ts +++ b/apps/meteor/app/livechat/server/api/lib/livechat.ts @@ -13,7 +13,6 @@ import { Meteor } from 'meteor/meteor'; import { callbacks } from '../../../../../lib/callbacks'; import { i18n } from '../../../../../server/lib/i18n'; import { normalizeAgent } from '../../lib/Helper'; -import { Livechat } from '../../lib/Livechat'; import { Livechat as LivechatTyped } from '../../lib/LivechatTyped'; export function online(department: string, skipSettingCheck = false, skipFallbackCheck = false): Promise { @@ -139,7 +138,7 @@ export function normalizeHttpHeaderData(headers: Record> { // Putting this ugly conversion while we type the livechat service - const initSettings = (await Livechat.getInitSettings()) as unknown as Record; + const initSettings = await LivechatTyped.getInitSettings(); const triggers = await findTriggers(); const departments = await findDepartments(businessUnit); const sound = `${Meteor.absoluteUrl()}sounds/chime.mp3`; diff --git a/apps/meteor/app/livechat/server/api/v1/message.ts b/apps/meteor/app/livechat/server/api/v1/message.ts index 104e2ece94d5..0d5a22b90d89 100644 --- a/apps/meteor/app/livechat/server/api/v1/message.ts +++ b/apps/meteor/app/livechat/server/api/v1/message.ts @@ -134,9 +134,9 @@ API.v1.addRoute( throw new Error('invalid-message'); } - const result = await Livechat.updateMessage({ + const result = await LivechatTyped.updateMessage({ guest, - message: { _id: msg._id, msg: this.bodyParams.msg }, + message: { _id: msg._id, msg: this.bodyParams.msg, rid: msg.rid }, }); if (!result) { return API.v1.failure(); diff --git a/apps/meteor/app/livechat/server/api/v1/offlineMessage.ts b/apps/meteor/app/livechat/server/api/v1/offlineMessage.ts index b01e60d2265f..6acd6ab98ea1 100644 --- a/apps/meteor/app/livechat/server/api/v1/offlineMessage.ts +++ b/apps/meteor/app/livechat/server/api/v1/offlineMessage.ts @@ -2,7 +2,7 @@ import { isPOSTLivechatOfflineMessageParams } from '@rocket.chat/rest-typings'; import { i18n } from '../../../../../server/lib/i18n'; import { API } from '../../../../api/server'; -import { Livechat } from '../../lib/Livechat'; +import { Livechat } from '../../lib/LivechatTyped'; API.v1.addRoute( 'livechat/offline.message', diff --git a/apps/meteor/app/livechat/server/api/v1/room.ts b/apps/meteor/app/livechat/server/api/v1/room.ts index 8f6151797463..4f3b4eb6234d 100644 --- a/apps/meteor/app/livechat/server/api/v1/room.ts +++ b/apps/meteor/app/livechat/server/api/v1/room.ts @@ -251,7 +251,7 @@ API.v1.addRoute( const { _id, username, name } = guest; const transferredBy = normalizeTransferredByData({ _id, username, name, userType: 'visitor' }, room); - if (!(await Livechat.transfer(room, guest, { roomId: rid, departmentId: department, transferredBy }))) { + if (!(await LivechatTyped.transfer(room, guest, { departmentId: department, transferredBy }))) { return API.v1.failure(); } @@ -312,10 +312,10 @@ API.v1.addRoute( { authRequired: true, permissionsRequired: ['view-l-room', 'transfer-livechat-guest'], validateParams: isLiveChatRoomForwardProps }, { async post() { - const transferData: typeof this.bodyParams & { - transferredBy?: unknown; + const transferData = this.bodyParams as typeof this.bodyParams & { + transferredBy: TransferByData; transferredTo?: { _id: string; username?: string; name?: string }; - } = this.bodyParams; + }; const room = await LivechatRooms.findOneById(this.bodyParams.roomId); if (!room || room.t !== 'l') { @@ -327,6 +327,10 @@ API.v1.addRoute( } const guest = await LivechatVisitors.findOneEnabledById(room.v?._id); + if (!guest) { + throw new Error('error-invalid-visitor'); + } + const transferedBy = this.user satisfies TransferByData; transferData.transferredBy = normalizeTransferredByData(transferedBy, room); if (transferData.userId) { @@ -340,7 +344,7 @@ API.v1.addRoute( } } - const chatForwardedResult = await Livechat.transfer(room, guest, transferData); + const chatForwardedResult = await LivechatTyped.transfer(room, guest, transferData); if (!chatForwardedResult) { throw new Error('error-forwarding-chat'); } diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index 75722e709b17..63cbbd6998ef 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -402,6 +402,9 @@ export const forwardRoomToAgent = async (room: IOmnichannelRoom, transferData: T logger.debug(`Forwarding room ${room._id} to agent ${transferData.userId}`); const { userId: agentId, clientAction } = transferData; + if (!agentId) { + throw new Error('error-invalid-agent'); + } const user = await Users.findOneOnlineAgentById(agentId); if (!user) { logger.debug(`Agent ${agentId} is offline. Cannot forward`); diff --git a/apps/meteor/app/livechat/server/lib/Livechat.js b/apps/meteor/app/livechat/server/lib/Livechat.js index e1d6626c7ddb..837a8eb7309b 100644 --- a/apps/meteor/app/livechat/server/lib/Livechat.js +++ b/apps/meteor/app/livechat/server/lib/Livechat.js @@ -1,21 +1,15 @@ // Note: Please don't add any new methods to this file, since its still in js and we are migrating to ts // Please add new methods to LivechatTyped.ts - -import dns from 'dns'; -import util from 'util'; - import { Message } from '@rocket.chat/core-services'; import { Logger } from '@rocket.chat/logger'; import { LivechatVisitors, LivechatCustomField, - Settings, LivechatRooms, LivechatInquiry, Subscriptions, Messages, LivechatDepartment as LivechatDepartmentRaw, - LivechatDepartmentAgents, Rooms, Users, ReadReceipts, @@ -34,7 +28,6 @@ import { hasPermissionAsync } from '../../../authorization/server/functions/hasP import { FileUpload } from '../../../file-upload/server'; import { deleteMessage } from '../../../lib/server/functions/deleteMessage'; import { sendMessage } from '../../../lib/server/functions/sendMessage'; -import { updateMessage } from '../../../lib/server/functions/updateMessage'; import * as Mailer from '../../../mailer/server/api'; import { settings } from '../../../settings/server'; import { businessHourManager } from '../business-hour'; @@ -45,8 +38,6 @@ import { RoutingManager } from './RoutingManager'; const logger = new Logger('Livechat'); -const dnsResolveMx = util.promisify(dns.resolveMx); - export const Livechat = { Analytics, @@ -63,28 +54,6 @@ export const Livechat = { }); }, - async updateMessage({ guest, message }) { - check(message, Match.ObjectIncluding({ _id: String })); - - const originalMessage = await Messages.findOneById(message._id); - if (!originalMessage || !originalMessage._id) { - return; - } - - const editAllowed = settings.get('Message_AllowEditing'); - const editOwn = originalMessage.u && originalMessage.u._id === guest._id; - - if (!editAllowed || !editOwn) { - throw new Meteor.Error('error-action-not-allowed', 'Message editing not allowed', { - method: 'livechatUpdateMessage', - }); - } - - await updateMessage(message, guest); - - return true; - }, - async deleteMessage({ guest, message }) { Livechat.logger.debug(`Attempting to delete a message by visitor ${guest._id}`); check(message, Match.ObjectIncluding({ _id: String })); @@ -188,50 +157,6 @@ export const Livechat = { return 0; }, - async getInitSettings() { - const rcSettings = {}; - - await Settings.findNotHiddenPublic([ - 'Livechat_title', - 'Livechat_title_color', - 'Livechat_enable_message_character_limit', - 'Livechat_message_character_limit', - 'Message_MaxAllowedSize', - 'Livechat_enabled', - 'Livechat_registration_form', - 'Livechat_allow_switching_departments', - 'Livechat_offline_title', - 'Livechat_offline_title_color', - 'Livechat_offline_message', - 'Livechat_offline_success_message', - 'Livechat_offline_form_unavailable', - 'Livechat_display_offline_form', - 'Omnichannel_call_provider', - 'Language', - 'Livechat_enable_transcript', - 'Livechat_transcript_message', - 'Livechat_fileupload_enabled', - 'FileUpload_Enabled', - 'Livechat_conversation_finished_message', - 'Livechat_conversation_finished_text', - 'Livechat_name_field_registration_form', - 'Livechat_email_field_registration_form', - 'Livechat_registration_form_message', - 'Livechat_force_accept_data_processing_consent', - 'Livechat_data_processing_consent_text', - 'Livechat_show_agent_info', - 'Livechat_clear_local_storage_when_chat_ended', - ]).forEach((setting) => { - rcSettings[setting._id] = setting.value; - }); - - rcSettings.Livechat_history_monitor_type = settings.get('Livechat_history_monitor_type'); - - rcSettings.Livechat_Show_Connecting = this.showConnecting(); - - return rcSettings; - }, - async saveRoomInfo(roomData, guestData, userId) { Livechat.logger.debug(`Saving room information on room ${roomData._id}`); const { livechatData = {} } = roomData; @@ -280,35 +205,6 @@ export const Livechat = { } }, - async closeOpenChats(userId, comment) { - Livechat.logger.debug(`Closing open chats for user ${userId}`); - const user = await Users.findOneById(userId); - - const extraQuery = await callbacks.run('livechat.applyDepartmentRestrictions', {}, { userId }); - const openChats = LivechatRooms.findOpenByAgent(userId, extraQuery); - const promises = []; - await openChats.forEach((room) => { - promises.push(LivechatTyped.closeRoom({ user, room, comment })); - }); - - await Promise.all(promises); - }, - - async forwardOpenChats(userId) { - Livechat.logger.debug(`Transferring open chats for user ${userId}`); - for await (const room of LivechatRooms.findOpenByAgent(userId)) { - const guest = await LivechatVisitors.findOneEnabledById(room.v._id); - const user = await Users.findOneById(userId); - const { _id, username, name } = user; - const transferredBy = normalizeTransferredByData({ _id, username, name }, room); - await this.transfer(room, guest, { - roomId: room._id, - transferredBy, - departmentId: guest.department, - }); - } - }, - async savePageHistory(token, roomId, pageInfo) { Livechat.logger.debug(`Saving page movement history for visitor with token ${token}`); if (pageInfo.change !== settings.get('Livechat_history_monitor_type')) { @@ -387,23 +283,6 @@ export const Livechat = { await sendMessage(transferredBy, transferMessage, room); }, - async transfer(room, guest, transferData) { - Livechat.logger.debug(`Transfering room ${room._id} [Transfered by: ${transferData?.transferredBy?._id}]`); - if (room.onHold) { - Livechat.logger.debug('Cannot transfer. Room is on hold'); - throw new Error('error-room-onHold'); - } - - if (transferData.departmentId) { - transferData.department = await LivechatDepartmentRaw.findOneById(transferData.departmentId, { - projection: { name: 1 }, - }); - Livechat.logger.debug(`Transfering room ${room._id} to department ${transferData.department?._id}`); - } - - return RoutingManager.transferRoom(room, guest, transferData); - }, - async returnRoomAsInquiry(rid, departmentId, overrideTransferData = {}) { Livechat.logger.debug(`Transfering room ${rid} to ${departmentId ? 'department' : ''} queue`); const room = await LivechatRooms.findOneById(rid); @@ -682,41 +561,6 @@ export const Livechat = { return updateDepartmentAgents(_id, departmentAgents, department.enabled); }, - /* - * @deprecated - Use the equivalent from DepartmentHelpers class - */ - async removeDepartment(_id) { - check(_id, String); - - const departmentRemovalEnabled = settings.get('Omnichannel_enable_department_removal'); - - if (!departmentRemovalEnabled) { - throw new Meteor.Error('department-removal-disabled', 'Department removal is disabled', { - method: 'livechat:removeDepartment', - }); - } - - const department = await LivechatDepartmentRaw.findOneById(_id, { projection: { _id: 1 } }); - - if (!department) { - throw new Meteor.Error('department-not-found', 'Department not found', { - method: 'livechat:removeDepartment', - }); - } - const ret = (await LivechatDepartmentRaw.removeById(_id)).deletedCount; - const agentsIds = (await LivechatDepartmentAgents.findByDepartmentId(_id, { projection: { agentId: 1 } }).toArray()).map( - (agent) => agent.agentId, - ); - await LivechatDepartmentAgents.removeByDepartmentId(_id); - await LivechatDepartmentRaw.unsetFallbackDepartmentByDepartmentId(_id); - if (ret) { - setImmediate(() => { - callbacks.run('livechat.afterRemoveDepartment', { department, agentsIds }); - }); - } - return ret; - }, - showConnecting() { const { showConnecting } = RoutingManager.getConfig(); return showConnecting; @@ -778,63 +622,6 @@ export const Livechat = { await LivechatRooms.updateVisitorStatus(token, status); }, - async sendOfflineMessage(data = {}) { - if (!settings.get('Livechat_display_offline_form')) { - throw new Error('error-offline-form-disabled'); - } - - const { message, name, email, department, host } = data; - const emailMessage = `${message}`.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1
$2'); - - let html = '

New livechat message

'; - if (host && host !== '') { - html = html.concat(`

Sent from: ${host}

`); - } - html = html.concat(` -

Visitor name: ${name}

-

Visitor email: ${email}

-

Message:
${emailMessage}

`); - - let fromEmail = settings.get('From_Email').match(/\b[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,4}\b/i); - - if (fromEmail) { - fromEmail = fromEmail[0]; - } else { - fromEmail = settings.get('From_Email'); - } - - if (settings.get('Livechat_validate_offline_email')) { - const emailDomain = email.substr(email.lastIndexOf('@') + 1); - - try { - await dnsResolveMx(emailDomain); - } catch (e) { - throw new Meteor.Error('error-invalid-email-address', 'Invalid email address', { - method: 'livechat:sendOfflineMessage', - }); - } - } - - // TODO Block offline form if Livechat_offline_email is undefined - // (it does not make sense to have an offline form that does nothing) - // `this.sendEmail` will throw an error if the email is invalid - // thus this breaks livechat, since the "to" email is invalid, and that returns an [invalid email] error to the livechat client - let emailTo = settings.get('Livechat_offline_email'); - if (department && department !== '') { - const dep = await LivechatDepartmentRaw.findOneByIdOrName(department); - emailTo = dep.email || emailTo; - } - - const from = `${name} - ${email} <${fromEmail}>`; - const replyTo = `${name} <${email}>`; - const subject = `Livechat offline message from ${name}: ${`${emailMessage}`.substring(0, 20)}`; - await this.sendEmail(from, emailTo, replyTo, subject, html); - - setImmediate(() => { - callbacks.run('livechat.offlineMessage', data); - }); - }, - async allowAgentChangeServiceStatus(statusLivechat, agentId) { if (statusLivechat !== 'available') { return true; diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index afb649488300..293b15e8d63c 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -1,3 +1,6 @@ +import dns from 'dns'; +import * as util from 'util'; + import { Message, VideoConf, api } from '@rocket.chat/core-services'; import type { IOmnichannelRoom, @@ -10,6 +13,8 @@ import type { ILivechatAgent, IMessage, ILivechatDepartment, + AtLeast, + TransferData, } from '@rocket.chat/core-typings'; import { UserStatus, isOmnichannelRoom } from '@rocket.chat/core-typings'; import { Logger, type MainLogger } from '@rocket.chat/logger'; @@ -24,6 +29,7 @@ import { LivechatDepartmentAgents, ReadReceipts, Rooms, + Settings, } from '@rocket.chat/models'; import { Random } from '@rocket.chat/random'; import { serverFetch as fetch } from '@rocket.chat/server-fetch'; @@ -41,7 +47,7 @@ import * as Mailer from '../../../mailer/server/api'; import { metrics } from '../../../metrics/server'; import { settings } from '../../../settings/server'; import { getTimezone } from '../../../utils/server/lib/getTimezone'; -import { updateDepartmentAgents, validateEmail } from './Helper'; +import { updateDepartmentAgents, validateEmail, normalizeTransferredByData } from './Helper'; import { QueueManager } from './QueueManager'; import { RoutingManager } from './RoutingManager'; @@ -75,6 +81,16 @@ export type CloseRoomParamsByVisitor = { export type CloseRoomParams = CloseRoomParamsByUser | CloseRoomParamsByVisitor; +type OfflineMessageData = { + message: string; + name: string; + email: string; + department?: string; + host?: string; +}; + +const dnsResolveMx = util.promisify(dns.resolveMx); + class LivechatClass { logger: Logger; @@ -917,6 +933,196 @@ class LivechatClass { await Promise.all([LivechatDepartmentAgents.enableAgentsByDepartmentId(_id), LivechatDepartment.unarchiveDepartment(_id)]); return true; } + + async updateMessage({ guest, message }: { guest: ILivechatVisitor; message: AtLeast }) { + check(message, Match.ObjectIncluding({ _id: String })); + + const originalMessage = await Messages.findOneById>(message._id, { projection: { u: 1 } }); + if (!originalMessage?._id) { + return; + } + + const editAllowed = settings.get('Message_AllowEditing'); + const editOwn = originalMessage.u && originalMessage.u._id === guest._id; + + if (!editAllowed || !editOwn) { + throw new Error('error-action-not-allowed'); + } + + // TODO: Apps sends an `any` object and apparently we just check for _id being present + // while updateMessage expects AtLeast + await updateMessage(message, guest as unknown as IUser); + + return true; + } + + async closeOpenChats(userId: string, comment?: string) { + this.logger.debug(`Closing open chats for user ${userId}`); + const user = await Users.findOneById(userId); + + const extraQuery = await callbacks.run('livechat.applyDepartmentRestrictions', {}, { userId }); + const openChats = LivechatRooms.findOpenByAgent(userId, extraQuery); + const promises: Promise[] = []; + await openChats.forEach((room) => { + promises.push(this.closeRoom({ user, room, comment })); + }); + + await Promise.all(promises); + } + + async transfer(room: IOmnichannelRoom, guest: ILivechatVisitor, transferData: TransferData) { + this.logger.debug(`Transfering room ${room._id} [Transfered by: ${transferData?.transferredBy?._id}]`); + if (room.onHold) { + throw new Error('error-room-onHold'); + } + + if (transferData.departmentId) { + const department = await LivechatDepartment.findOneById(transferData.departmentId, { + projection: { name: 1 }, + }); + if (!department) { + throw new Error('error-invalid-department'); + } + + transferData.department = department; + this.logger.debug(`Transfering room ${room._id} to department ${transferData.department?._id}`); + } + + return RoutingManager.transferRoom(room, guest, transferData); + } + + async forwardOpenChats(userId: string) { + this.logger.debug(`Transferring open chats for user ${userId}`); + const user = await Users.findOneById(userId); + if (!user) { + throw new Error('error-invalid-user'); + } + + const { _id, username, name } = user; + for await (const room of LivechatRooms.findOpenByAgent(userId)) { + const guest = await LivechatVisitors.findOneEnabledById(room.v._id); + if (!guest) { + continue; + } + + const transferredBy = normalizeTransferredByData({ _id, username, name }, room); + await this.transfer(room, guest, { + transferredBy, + departmentId: guest.department, + }); + } + } + + showConnecting() { + return RoutingManager.getConfig()?.showConnecting || false; + } + + async getInitSettings() { + const rcSettings: Record = {}; + + await Settings.findNotHiddenPublic([ + 'Livechat_title', + 'Livechat_title_color', + 'Livechat_enable_message_character_limit', + 'Livechat_message_character_limit', + 'Message_MaxAllowedSize', + 'Livechat_enabled', + 'Livechat_registration_form', + 'Livechat_allow_switching_departments', + 'Livechat_offline_title', + 'Livechat_offline_title_color', + 'Livechat_offline_message', + 'Livechat_offline_success_message', + 'Livechat_offline_form_unavailable', + 'Livechat_display_offline_form', + 'Omnichannel_call_provider', + 'Language', + 'Livechat_enable_transcript', + 'Livechat_transcript_message', + 'Livechat_fileupload_enabled', + 'FileUpload_Enabled', + 'Livechat_conversation_finished_message', + 'Livechat_conversation_finished_text', + 'Livechat_name_field_registration_form', + 'Livechat_email_field_registration_form', + 'Livechat_registration_form_message', + 'Livechat_force_accept_data_processing_consent', + 'Livechat_data_processing_consent_text', + 'Livechat_show_agent_info', + 'Livechat_clear_local_storage_when_chat_ended', + ]).forEach((setting) => { + rcSettings[setting._id] = setting.value; + }); + + rcSettings.Livechat_history_monitor_type = settings.get('Livechat_history_monitor_type'); + + rcSettings.Livechat_Show_Connecting = this.showConnecting(); + + return rcSettings; + } + + async sendOfflineMessage(data: OfflineMessageData) { + if (!settings.get('Livechat_display_offline_form')) { + throw new Error('error-offline-form-disabled'); + } + + const { message, name, email, department, host } = data; + + if (!email) { + throw new Error('error-invalid-email'); + } + + const emailMessage = `${message}`.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1
$2'); + + let html = '

New livechat message

'; + if (host && host !== '') { + html = html.concat(`

Sent from: ${host}

`); + } + html = html.concat(` +

Visitor name: ${name}

+

Visitor email: ${email}

+

Message:
${emailMessage}

`); + + const fromEmail = settings.get('From_Email').match(/\b[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,4}\b/i); + + let from: string; + if (fromEmail) { + from = fromEmail[0]; + } else { + from = settings.get('From_Email'); + } + + if (settings.get('Livechat_validate_offline_email')) { + const emailDomain = email.substr(email.lastIndexOf('@') + 1); + + try { + await dnsResolveMx(emailDomain); + } catch (e) { + throw new Meteor.Error('error-invalid-email-address'); + } + } + + // TODO Block offline form if Livechat_offline_email is undefined + // (it does not make sense to have an offline form that does nothing) + // `this.sendEmail` will throw an error if the email is invalid + // thus this breaks livechat, since the "to" email is invalid, and that returns an [invalid email] error to the livechat client + let emailTo = settings.get('Livechat_offline_email'); + if (department && department !== '') { + const dep = await LivechatDepartment.findOneByIdOrName(department, { projection: { email: 1 } }); + if (dep) { + emailTo = dep.email || emailTo; + } + } + + const fromText = `${name} - ${email} <${from}>`; + const replyTo = `${name} <${email}>`; + const subject = `Livechat offline message from ${name}: ${`${emailMessage}`.substring(0, 20)}`; + await this.sendEmail(fromText, emailTo, replyTo, subject, html); + + setImmediate(() => { + void callbacks.run('livechat.offlineMessage', data); + }); + } } export const Livechat = new LivechatClass(); diff --git a/apps/meteor/app/livechat/server/lib/stream/agentStatus.ts b/apps/meteor/app/livechat/server/lib/stream/agentStatus.ts index bbce5d16efb4..5ddd25e90bd2 100644 --- a/apps/meteor/app/livechat/server/lib/stream/agentStatus.ts +++ b/apps/meteor/app/livechat/server/lib/stream/agentStatus.ts @@ -1,7 +1,7 @@ import { Logger } from '@rocket.chat/logger'; import { settings } from '../../../../settings/server'; -import { Livechat } from '../Livechat'; +import { Livechat } from '../LivechatTyped'; const logger = new Logger('AgentStatusWatcher'); diff --git a/apps/meteor/app/livechat/server/methods/sendOfflineMessage.ts b/apps/meteor/app/livechat/server/methods/sendOfflineMessage.ts index 9a475de5e32d..c3b5537f31be 100644 --- a/apps/meteor/app/livechat/server/methods/sendOfflineMessage.ts +++ b/apps/meteor/app/livechat/server/methods/sendOfflineMessage.ts @@ -4,7 +4,7 @@ import { DDPRateLimiter } from 'meteor/ddp-rate-limiter'; import { Meteor } from 'meteor/meteor'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; -import { Livechat } from '../lib/Livechat'; +import { Livechat } from '../lib/LivechatTyped'; declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention diff --git a/apps/meteor/app/livechat/server/methods/setDepartmentForVisitor.ts b/apps/meteor/app/livechat/server/methods/setDepartmentForVisitor.ts index 61e6b21267da..a14933ed8d47 100644 --- a/apps/meteor/app/livechat/server/methods/setDepartmentForVisitor.ts +++ b/apps/meteor/app/livechat/server/methods/setDepartmentForVisitor.ts @@ -5,7 +5,7 @@ import { Meteor } from 'meteor/meteor'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { normalizeTransferredByData } from '../lib/Helper'; -import { Livechat } from '../lib/Livechat'; +import { Livechat } from '../lib/LivechatTyped'; declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention diff --git a/apps/meteor/app/livechat/server/methods/transfer.ts b/apps/meteor/app/livechat/server/methods/transfer.ts index 3817b10bf42b..16ee1abc6191 100644 --- a/apps/meteor/app/livechat/server/methods/transfer.ts +++ b/apps/meteor/app/livechat/server/methods/transfer.ts @@ -7,7 +7,7 @@ import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; import { methodDeprecationLogger } from '../../../lib/server/lib/deprecationWarningLogger'; import { normalizeTransferredByData } from '../lib/Helper'; -import { Livechat } from '../lib/Livechat'; +import { Livechat } from '../lib/LivechatTyped'; declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -60,6 +60,10 @@ Meteor.methods({ const guest = await LivechatVisitors.findOneEnabledById(room.v?._id); + if (!guest) { + throw new Meteor.Error('error-invalid-visitor', 'Invalid visitor', { method: 'livechat:transfer' }); + } + const user = await Meteor.userAsync(); if (!user) { diff --git a/packages/core-typings/src/omnichannel/routing.ts b/packages/core-typings/src/omnichannel/routing.ts index eed6dd6f1a19..43ca0c08f5d2 100644 --- a/packages/core-typings/src/omnichannel/routing.ts +++ b/packages/core-typings/src/omnichannel/routing.ts @@ -24,7 +24,7 @@ export interface IRoutingMethod { } export type TransferData = { - userId: string; + userId?: string; departmentId?: string; department?: Pick; transferredBy: { @@ -36,7 +36,7 @@ export type TransferData = { name?: string; }; clientAction?: boolean; - scope: 'agent' | 'department' | 'queue' | 'autoTransferUnansweredChatsToAgent' | 'autoTransferUnansweredChatsToQueue'; + scope?: 'agent' | 'department' | 'queue' | 'autoTransferUnansweredChatsToAgent' | 'autoTransferUnansweredChatsToQueue'; comment?: string; };