Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Move functions out of Livechat.js #30650

Merged
merged 10 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions apps/meteor/app/apps/server/bridges/livechat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ILivechatRoom> {
Expand Down Expand Up @@ -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 },
Expand Down
3 changes: 1 addition & 2 deletions apps/meteor/app/livechat/server/api/lib/livechat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean> {
Expand Down Expand Up @@ -139,7 +138,7 @@ export function normalizeHttpHeaderData(headers: Record<string, string | string[

export async function settings({ businessUnit = '' }: { businessUnit?: string } = {}): Promise<Record<string, string | number | any>> {
// Putting this ugly conversion while we type the livechat service
const initSettings = (await Livechat.getInitSettings()) as unknown as Record<string, string | number | any>;
const initSettings = await LivechatTyped.getInitSettings();
const triggers = await findTriggers();
const departments = await findDepartments(businessUnit);
const sound = `${Meteor.absoluteUrl()}sounds/chime.mp3`;
Expand Down
4 changes: 2 additions & 2 deletions apps/meteor/app/livechat/server/api/v1/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/livechat/server/api/v1/offlineMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
14 changes: 9 additions & 5 deletions apps/meteor/app/livechat/server/api/v1/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down Expand Up @@ -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') {
Expand All @@ -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) {
Expand All @@ -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');
}
Expand Down
3 changes: 3 additions & 0 deletions apps/meteor/app/livechat/server/lib/Helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`);
Expand Down
213 changes: 0 additions & 213 deletions apps/meteor/app/livechat/server/lib/Livechat.js
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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';
Expand All @@ -45,8 +38,6 @@ import { RoutingManager } from './RoutingManager';

const logger = new Logger('Livechat');

const dnsResolveMx = util.promisify(dns.resolveMx);

export const Livechat = {
Analytics,

Expand All @@ -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 }));
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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')) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<br>$2');

let html = '<h1>New livechat message</h1>';
if (host && host !== '') {
html = html.concat(`<p><strong>Sent from:</strong><a href='${host}'> ${host}</a></p>`);
}
html = html.concat(`
<p><strong>Visitor name:</strong> ${name}</p>
<p><strong>Visitor email:</strong> ${email}</p>
<p><strong>Message:</strong><br>${emailMessage}</p>`);

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;
Expand Down
Loading
Loading