import dns from 'node:dns'; import * as util from 'node:util'; import type { ILivechatVisitor, AtLeast, IMessage, IUser, IOmnichannelRoomInfo, SelectedAgent } from '@rocket.chat/core-typings'; import { LivechatDepartment, Messages } from '@rocket.chat/models'; import { check, Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import type { ILivechatMessage } from './localTypes'; import { getRoom } from './rooms'; import { showConnecting } from './utils'; import { settings } from '../../settings'; import { callbacks } from '../callbacks'; import { deleteMessage as deleteMessageFunc } from '../messages/deleteMessage'; import { sendMessage as sendMessageFunc } from '../messages/sendMessage'; import { updateMessage as updateMessageFunc } from '../messages/updateMessage'; import * as Mailer from '../notifications/email/api'; const dnsResolveMx = util.promisify(dns.resolveMx); type OfflineMessageData = { message: string; name: string; email: string; department?: string; host?: string; }; export async function 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 Mailer.send({ to: emailTo, from: fromText, replyTo, subject, html, }); setImmediate(() => { void callbacks.run('livechat.offlineMessage', data); }); } export async function updateMessage({ guest, message }: { guest: ILivechatVisitor; message: AtLeast }) { // TODO: Remove check check(message, Match.ObjectIncluding({ _id: String })); const originalMessage = await Messages.findOneById>(message._id, { projection: { u: 1 } }); if (!originalMessage?._id) { return; } // TODO: shouldn't this happen inside updateMessageFunc? 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 updateMessageFunc(message, guest as unknown as IUser); return true; } export async function deleteMessage({ guest, message }: { guest: ILivechatVisitor; message: IMessage }) { const deleteAllowed = settings.get('Message_AllowDeleting'); const editOwn = message.u && message.u._id === guest._id; if (!deleteAllowed || !editOwn) { throw new Error('error-action-not-allowed'); } // TODO: we shouldn't do this :( await deleteMessageFunc(message, guest as unknown as IUser); return true; } export async function sendMessage({ guest, message, roomInfo, agent, }: { guest: ILivechatVisitor; message: ILivechatMessage; roomInfo: IOmnichannelRoomInfo; agent?: SelectedAgent; }) { const { room, newRoom } = await getRoom(guest, message, roomInfo, agent); return { ...(await sendMessageFunc(guest, { ...message, token: guest.token, ...(guest.name && { alias: guest.name }) }, room)), newRoom, showConnecting: showConnecting(), }; }