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(),
};
}