diff --git a/app/livechat/imports/server/rest/upload.js b/app/livechat/imports/server/rest/upload.js index ca89ec139e9..b51e9318103 100644 --- a/app/livechat/imports/server/rest/upload.js +++ b/app/livechat/imports/server/rest/upload.js @@ -3,7 +3,7 @@ import filesize from 'filesize'; import { settings } from '../../../../settings'; import { Settings, LivechatRooms, LivechatVisitors } from '../../../../models'; -import { fileUploadIsValidContentType } from '../../../../utils'; +import { fileUploadIsValidContentType } from '../../../../utils/server'; import { FileUpload } from '../../../../file-upload'; import { API } from '../../../../api/server'; import { getUploadFormData } from '../../../../api/server/lib/getUploadFormData'; diff --git a/app/livechat/server/api/lib/livechat.js b/app/livechat/server/api/lib/livechat.js index bb4a7b4bb75..a7a29598250 100644 --- a/app/livechat/server/api/lib/livechat.js +++ b/app/livechat/server/api/lib/livechat.js @@ -1,8 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; -import _ from 'underscore'; -import { LivechatRooms, LivechatVisitors, LivechatDepartment, LivechatTrigger } from '../../../../models'; +import { LivechatRooms, LivechatVisitors, LivechatDepartment, LivechatTrigger, EmojiCustom } from '../../../../models/server'; import { Livechat } from '../../lib/Livechat'; import { callbacks } from '../../../../callbacks/server'; import { normalizeAgent } from '../../lib/Helper'; @@ -12,11 +11,13 @@ export function online(department) { } export function findTriggers() { - return LivechatTrigger.findEnabled().fetch().map((trigger) => _.pick(trigger, '_id', 'actions', 'conditions', 'runOnce')); + return LivechatTrigger.findEnabled().fetch().map(({ _id, actions, conditions, runOnce }) => ({ _id, actions, conditions, runOnce })); } export function findDepartments() { - return LivechatDepartment.findEnabledWithAgents().fetch().map((department) => _.pick(department, '_id', 'name', 'showOnRegistration', 'showOnOfflineForm')); + return LivechatDepartment.findEnabledWithAgents({ + _id: 1, name: 1, showOnRegistration: 1, showOnOfflineForm: 1, + }).fetch().map(({ _id, name, showOnRegistration, showOnOfflineForm }) => ({ _id, name, showOnRegistration, showOnOfflineForm })); } export function findGuest(token) { @@ -57,13 +58,10 @@ export function findOpenRoom(token, departmentId) { }, }; - let room; const rooms = departmentId ? LivechatRooms.findOpenByVisitorTokenAndDepartmentId(token, departmentId, options).fetch() : LivechatRooms.findOpenByVisitorToken(token, options).fetch(); if (rooms && rooms.length > 0) { - room = rooms[0]; + return rooms[0]; } - - return room; } export function getRoom({ guest, rid, roomInfo, agent, extraParams }) { @@ -93,7 +91,7 @@ export function settings() { const triggers = findTriggers(); const departments = findDepartments(); const sound = `${ Meteor.absoluteUrl() }sounds/chime.mp3`; - const emojis = Meteor.call('listEmojiCustom'); + const emojis = EmojiCustom.find().fetch(); return { enabled: initSettings.Livechat_enabled, settings: { diff --git a/app/livechat/server/api/v1/config.js b/app/livechat/server/api/v1/config.js index 85ef7a10a86..51a6f7b04c9 100644 --- a/app/livechat/server/api/v1/config.js +++ b/app/livechat/server/api/v1/config.js @@ -1,7 +1,8 @@ import { Match, check } from 'meteor/check'; import { API } from '../../../../api/server'; -import { findGuest, settings, online, findOpenRoom, getExtraConfigInfo, findAgent } from '../lib/livechat'; +import { Livechat } from '../../lib/Livechat'; +import { settings, findOpenRoom, getExtraConfigInfo, findAgent } from '../lib/livechat'; API.v1.addRoute('livechat/config', { get() { @@ -10,28 +11,25 @@ API.v1.addRoute('livechat/config', { token: Match.Maybe(String), department: Match.Maybe(String), }); + const enabled = Livechat.enabled(); - const config = settings(); - if (!config.enabled) { + if (!enabled) { return API.v1.success({ config: { enabled: false } }); } + const config = settings(); + const { token, department } = this.queryParams; - const status = online(department); - const guest = token && findGuest(token); + const status = Livechat.online(department); + const guest = token && Livechat.findGuest(token); - let room; - let agent; + const room = guest && findOpenRoom(token); + const agent = guest && room && room.servedBy && findAgent(room.servedBy._id); - if (guest) { - room = findOpenRoom(token); - agent = room && room.servedBy && findAgent(room.servedBy._id); - } const extra = Promise.await(getExtraConfigInfo(room)); const { config: extraConfig = {} } = extra || {}; - Object.assign(config, { online: status, guest, room, agent }, { ...extraConfig }); - return API.v1.success({ config }); + return API.v1.success({ config: { ...config, online: status, guest, room, agent, ...extraConfig } }); } catch (e) { return API.v1.failure(e); } diff --git a/app/livechat/server/api/v1/room.js b/app/livechat/server/api/v1/room.js index 8ce9d42014a..eac9866fcb0 100644 --- a/app/livechat/server/api/v1/room.js +++ b/app/livechat/server/api/v1/room.js @@ -36,7 +36,7 @@ API.v1.addRoute('livechat/room', { const agentObj = agentId && findAgent(agentId); if (agentObj) { const { username } = agentObj; - agent = Object.assign({}, { agentId, username }); + agent = { agentId, username }; } const rid = roomId || Random.id(); @@ -199,12 +199,12 @@ API.v1.addRoute('livechat/room.visitor', { authRequired: true }, { throw new Meteor.Error('invalid-visitor'); } - let room = LivechatRooms.findOneById(rid, { _id: 1 }); + let room = LivechatRooms.findOneById(rid, { _id: 1 }); // TODO: check _id if (!room) { throw new Meteor.Error('invalid-room'); } - const { v: { _id: roomVisitorId } = {} } = room; + const { v: { _id: roomVisitorId } = {} } = room; // TODO: v it will be undefined if (roomVisitorId !== oldVisitorId) { throw new Meteor.Error('invalid-room-visitor'); } diff --git a/app/livechat/server/lib/Livechat.js b/app/livechat/server/lib/Livechat.js index 10aad0a7a14..29c4bdeffa1 100644 --- a/app/livechat/server/lib/Livechat.js +++ b/app/livechat/server/lib/Livechat.js @@ -13,8 +13,8 @@ import UAParser from 'ua-parser-js'; import { QueueManager } from './QueueManager'; import { RoutingManager } from './RoutingManager'; import { Analytics } from './Analytics'; -import { settings } from '../../../settings'; -import { callbacks } from '../../../callbacks'; +import { settings } from '../../../settings/server'; +import { callbacks } from '../../../callbacks/server'; import { Users, LivechatRooms, @@ -27,7 +27,7 @@ import { LivechatCustomField, LivechatVisitors, LivechatInquiry, -} from '../../../models'; +} from '../../../models/server'; import { Logger } from '../../../logger'; import { addUserRoles, hasPermission, hasRole, removeUserFromRoles, canAccessRoom } from '../../../authorization'; import * as Mailer from '../../../mailer'; @@ -50,6 +50,19 @@ export const Livechat = { }, }), + + findGuest(token) { + return LivechatVisitors.getVisitorByToken(token, { + fields: { + name: 1, + username: 1, + token: 1, + visitorEmails: 1, + department: 1, + }, + }); + }, + online(department) { if (settings.get('Livechat_accept_chats_with_no_agents')) { return true; @@ -57,13 +70,12 @@ export const Livechat = { if (settings.get('Livechat_assign_new_conversation_to_bot')) { const botAgents = Livechat.getBotAgents(department); - if (botAgents && botAgents.count() > 0) { + if (botAgents.count() > 0) { return true; } } - const onlineAgents = Livechat.getOnlineAgents(department); - return onlineAgents && onlineAgents.count() > 0; + return Livechat.checkOnlineAgents(department); }, getNextAgent(department) { @@ -89,6 +101,18 @@ export const Livechat = { return Users.findOnlineAgents(); }, + checkOnlineAgents(department, agent) { + if (agent?.agentId) { + return Users.checkOnlineAgents(agent.agentId); + } + + if (department) { + return LivechatDepartmentAgents.checkOnlineForDepartment(department); + } + + return Users.checkOnlineAgents(); + }, + getBotAgents(department) { if (department) { return LivechatDepartmentAgents.getBotsForDepartment(department); @@ -383,8 +407,8 @@ export const Livechat = { */ Apps.getBridges().getListenerBridge().livechatEvent(AppEvents.ILivechatRoomClosedHandler, room); Apps.getBridges().getListenerBridge().livechatEvent(AppEvents.IPostLivechatRoomClosed, room); - callbacks.runAsync('livechat.closeRoom', room); }); + callbacks.runAsync('livechat.closeRoom', room); return true; }, @@ -427,6 +451,10 @@ export const Livechat = { return LivechatVisitors.updateLivechatDataByToken(token, key, value, overwrite); }, + enabled() { + return settings.get('Livechat_enabled'); + }, + getInitSettings() { const rcSettings = {}; @@ -464,9 +492,7 @@ export const Livechat = { rcSettings[setting._id] = setting.value; }); - settings.get('Livechat_history_monitor_type', (key, value) => { - rcSettings[key] = value; - }); + rcSettings.Livechat_history_monitor_type = settings.get('Livechat_history_monitor_type'); rcSettings.Livechat_Show_Connecting = this.showConnecting(); @@ -501,8 +527,8 @@ export const Livechat = { Meteor.defer(() => { Apps.triggerEvent(AppEvents.IPostLivechatRoomSaved, roomData._id); - callbacks.run('livechat.saveRoom', roomData); }); + callbacks.runAsync('livechat.saveRoom', roomData); if (!_.isEmpty(guestData.name)) { const { _id: rid } = roomData; @@ -533,30 +559,31 @@ export const Livechat = { }, savePageHistory(token, roomId, pageInfo) { - if (pageInfo.change === Livechat.historyMonitorType) { - const user = Users.findOneById('rocket.cat'); - - const pageTitle = pageInfo.title; - const pageUrl = pageInfo.location.href; - const extraData = { - navigation: { - page: pageInfo, - token, - }, - }; + if (pageInfo.change !== Livechat.historyMonitorType) { + return; + } + const user = Users.findOneById('rocket.cat'); - if (!roomId) { - // keep history of unregistered visitors for 1 month - const keepHistoryMiliseconds = 2592000000; - extraData.expireAt = new Date().getTime() + keepHistoryMiliseconds; - } + const pageTitle = pageInfo.title; + const pageUrl = pageInfo.location.href; + const extraData = { + navigation: { + page: pageInfo, + token, + }, + }; - if (!settings.get('Livechat_Visitor_navigation_as_a_message')) { - extraData._hidden = true; - } + if (!roomId) { + // keep history of unregistered visitors for 1 month + const keepHistoryMiliseconds = 2592000000; + extraData.expireAt = new Date().getTime() + keepHistoryMiliseconds; + } - return Messages.createNavigationHistoryWithRoomIdMessageAndUser(roomId, `${ pageTitle } - ${ pageUrl }`, user, extraData); + if (!settings.get('Livechat_Visitor_navigation_as_a_message')) { + extraData._hidden = true; } + + return Messages.createNavigationHistoryWithRoomIdMessageAndUser(roomId, `${ pageTitle } - ${ pageUrl }`, user, extraData); }, saveTransferHistory(room, transferData) { @@ -638,31 +665,27 @@ export const Livechat = { throw new Meteor.Error('error-returning-inquiry', 'Error returning inquiry to the queue', { method: 'livechat:returnRoomAsInquiry' }); } - Meteor.defer(() => { - callbacks.run('livechat:afterReturnRoomAsInquiry', { room }); - }); + callbacks.runAsync('livechat:afterReturnRoomAsInquiry', { room }); return true; }, - sendRequest(postData, callback, trying = 1) { + sendRequest(postData, callback, attempts = 10) { + if (!attempts) { + return; + } + const secretToken = settings.get('Livechat_secret_token'); + const headers = { headers: { 'X-RocketChat-Livechat-Token': secretToken } }; + const options = { data: postData, ...secretToken !== '' && secretToken !== undefined && { headers } }; try { - const options = { data: postData }; - const secretToken = settings.get('Livechat_secret_token'); - if (secretToken !== '' && secretToken !== undefined) { - Object.assign(options, { headers: { 'X-RocketChat-Livechat-Token': secretToken } }); - } return HTTP.post(settings.get('Livechat_webhookUrl'), options); } catch (e) { - Livechat.logger.webhook.error(`Response error on ${ trying } try ->`, e); + Livechat.logger.webhook.error(`Response error on ${ 11 - attempts } try ->`, e); // try 10 times after 10 seconds each - if (trying < 10) { - Livechat.logger.webhook.warn('Will try again in 10 seconds ...'); - trying++; - setTimeout(Meteor.bindEnvironment(() => { - Livechat.sendRequest(postData, callback, trying); - }), 10000); - } + Livechat.logger.webhook.warn('Will try again in 10 seconds ...'); + setTimeout(Meteor.bindEnvironment(function() { + Livechat.sendRequest(postData, callback, attempts--); + }), 10000); } }, diff --git a/app/models/server/models/LivechatDepartment.js b/app/models/server/models/LivechatDepartment.js index b5c826dfd55..75f9a79d64c 100644 --- a/app/models/server/models/LivechatDepartment.js +++ b/app/models/server/models/LivechatDepartment.js @@ -92,12 +92,12 @@ export class LivechatDepartment extends Base { return this.remove(query); } - findEnabledWithAgents() { + findEnabledWithAgents(fields = null) { const query = { numAgents: { $gt: 0 }, enabled: true, }; - return this.find(query); + return this.find(query, fields && { fields }); } findOneByIdOrName(_idOrName, options) { diff --git a/app/models/server/models/LivechatDepartmentAgents.js b/app/models/server/models/LivechatDepartmentAgents.js index 48e27eff061..69f17668601 100644 --- a/app/models/server/models/LivechatDepartmentAgents.js +++ b/app/models/server/models/LivechatDepartmentAgents.js @@ -85,9 +85,8 @@ export class LivechatDepartmentAgents extends Base { }; const collectionObj = this.model.rawCollection(); - const findAndModify = Meteor.wrapAsync(collectionObj.findAndModify, collectionObj); - const agent = findAndModify(query, sort, update); + const agent = Promise.await(collectionObj.findAndModify(query, sort, update)); if (agent && agent.value) { return { agentId: agent.value.agentId, @@ -97,6 +96,19 @@ export class LivechatDepartmentAgents extends Base { return null; } + + checkOnlineForDepartment(departmentId) { + const agents = this.findByDepartmentId(departmentId).fetch(); + + if (agents.length === 0) { + return false; + } + + const onlineUser = Users.findOneOnlineAgentByUsername(_.pluck(agents, 'username')); + + return Boolean(onlineUser); + } + getOnlineForDepartment(departmentId) { const agents = this.findByDepartmentId(departmentId).fetch(); diff --git a/app/models/server/models/Users.js b/app/models/server/models/Users.js index 575e7125466..043d39bf5e0 100644 --- a/app/models/server/models/Users.js +++ b/app/models/server/models/Users.js @@ -8,23 +8,16 @@ import { Base } from './_Base'; import Subscriptions from './Subscriptions'; import { settings } from '../../../settings/server/functions/settings'; -const queryStatusAgentOnline = (extraFilters = {}) => { - if (settings.get('Livechat_enabled_when_agent_idle') === false) { - extraFilters = Object.assign(extraFilters, { statusConnection: { $ne: 'away' } }); - } - - const query = { - status: { - $exists: true, - $ne: 'offline', - }, - statusLivechat: 'available', - roles: 'livechat-agent', - ...extraFilters, - }; - - return query; -}; +const queryStatusAgentOnline = (extraFilters = {}) => ({ + status: { + $exists: true, + $ne: 'offline', + }, + statusLivechat: 'available', + roles: 'livechat-agent', + ...extraFilters, + ...settings.get('Livechat_enabled_when_agent_idle') === false && { statusConnection: { $ne: 'away' } }, +}); export class Users extends Base { constructor(...args) { super(...args); @@ -95,7 +88,7 @@ export class Users extends Base { return this.findOne(query); } - setOperator(_id, operator) { + setOperator(_id, operator) { // TODO:: Create class Agent const update = { $set: { operator, @@ -105,13 +98,19 @@ export class Users extends Base { return this.update(_id, update); } - findOnlineAgents(agentId) { + checkOnlineAgents(agentId) { // TODO:: Create class Agent + const query = queryStatusAgentOnline(agentId && { _id: agentId }); + + return Boolean(this.findOne(query)); + } + + findOnlineAgents(agentId) { // TODO:: Create class Agent const query = queryStatusAgentOnline(agentId && { _id: agentId }); return this.find(query); } - findBotAgents(usernameList) { + findBotAgents(usernameList) { // TODO:: Create class Agent const query = { roles: { $all: ['bot', 'livechat-agent'], @@ -126,7 +125,7 @@ export class Users extends Base { return this.find(query); } - findOneBotAgent() { + findOneBotAgent() { // TODO:: Create class Agent const query = { roles: { $all: ['bot', 'livechat-agent'], @@ -136,19 +135,19 @@ export class Users extends Base { return this.findOne(query); } - findOneOnlineAgentByUsername(username, options) { + findOneOnlineAgentByUsername(username, options) { // TODO:: Create class Agent const query = queryStatusAgentOnline({ username }); return this.findOne(query, options); } - findOneOnlineAgentById(_id) { + findOneOnlineAgentById(_id) { // TODO: Create class Agent const query = queryStatusAgentOnline({ _id }); return this.findOne(query); } - findOneAgentById(_id, options) { + findOneAgentById(_id, options) { // TODO: Create class Agent const query = { _id, roles: 'livechat-agent', @@ -157,7 +156,7 @@ export class Users extends Base { return this.findOne(query, options); } - findAgents() { + findAgents() { // TODO: Create class Agent const query = { roles: 'livechat-agent', }; @@ -165,7 +164,7 @@ export class Users extends Base { return this.find(query); } - findOnlineUserFromList(userList) { + findOnlineUserFromList(userList) { // TODO: Create class Agent const username = { $in: [].concat(userList), }; @@ -175,7 +174,7 @@ export class Users extends Base { return this.find(query); } - getNextAgent(ignoreAgentId) { + getNextAgent(ignoreAgentId) { // TODO: Create class Agent const extraFilters = { ...ignoreAgentId && { _id: { $ne: ignoreAgentId } }, }; @@ -205,7 +204,7 @@ export class Users extends Base { return null; } - getNextBotAgent(ignoreAgentId) { + getNextBotAgent(ignoreAgentId) { // TODO: Create class Agent const query = { roles: { $all: ['bot', 'livechat-agent'], @@ -237,7 +236,7 @@ export class Users extends Base { return null; } - setLivechatStatus(userId, status) { + setLivechatStatus(userId, status) { // TODO: Create class Agent const query = { _id: userId, }; @@ -251,7 +250,7 @@ export class Users extends Base { return this.update(query, update); } - setLivechatData(userId, data = {}) { + setLivechatData(userId, data = {}) { // TODO: Create class Agent const query = { _id: userId, }; @@ -265,15 +264,15 @@ export class Users extends Base { return this.update(query, update); } - closeOffice() { + closeOffice() { // TODO: Create class Agent this.findAgents().forEach((agent) => this.setLivechatStatus(agent._id, 'not-available')); } - openOffice() { + openOffice() { // TODO: Create class Agent this.findAgents().forEach((agent) => this.setLivechatStatus(agent._id, 'available')); } - getAgentInfo(agentId) { + getAgentInfo(agentId) { // TODO: Create class Agent const query = { _id: agentId, }; diff --git a/ee/app/livechat-enterprise/server/hooks/onLoadConfigApi.js b/ee/app/livechat-enterprise/server/hooks/onLoadConfigApi.js index 7bf3f416c6c..e3c2fc4f7bf 100644 --- a/ee/app/livechat-enterprise/server/hooks/onLoadConfigApi.js +++ b/ee/app/livechat-enterprise/server/hooks/onLoadConfigApi.js @@ -7,10 +7,9 @@ callbacks.add('livechat.onLoadConfigApi', async (options = {}) => { const queueInfo = await getLivechatQueueInfo(room); const customFields = getLivechatCustomFields(); - const config = { + return { ...queueInfo && { queueInfo }, ...customFields && { customFields }, + ...options, }; - - return Object.assign({ config }, options); }, callbacks.priority.MEDIUM, 'livechat-on-load-config-api');