snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
541
apps/meteor/server/api/v1/omnichannel/room.ts
Normal file
541
apps/meteor/server/api/v1/omnichannel/room.ts
Normal file
@@ -0,0 +1,541 @@
|
||||
import { Omnichannel } from '@rocket.chat/core-services';
|
||||
import type {
|
||||
ILivechatAgent,
|
||||
IOmnichannelInquiryExtraData,
|
||||
IOmnichannelRoom,
|
||||
IUser,
|
||||
SelectedAgent,
|
||||
TransferByData,
|
||||
TransferData,
|
||||
} from '@rocket.chat/core-typings';
|
||||
import { isOmnichannelRoom, OmnichannelSourceType } from '@rocket.chat/core-typings';
|
||||
import { LivechatVisitors, Users, LivechatRooms } from '@rocket.chat/models';
|
||||
import {
|
||||
isLiveChatRoomForwardProps,
|
||||
isPOSTLivechatRoomCloseParams,
|
||||
isPOSTLivechatRoomSurveyParams,
|
||||
isLiveChatRoomJoinProps,
|
||||
isLiveChatRoomSaveInfoProps,
|
||||
isPOSTLivechatRoomCloseByUserParams,
|
||||
isPOSTLivechatRoomsCloseAll,
|
||||
isPOSTLivechatRoomsCloseAllSuccessResponse,
|
||||
POSTLivechatRemoveRoomSuccess,
|
||||
isPOSTLivechatRemoveRoomParams,
|
||||
validateBadRequestErrorResponse,
|
||||
validateUnauthorizedErrorResponse,
|
||||
validateForbiddenErrorResponse,
|
||||
ajv,
|
||||
} from '@rocket.chat/rest-typings';
|
||||
import { isPOSTLivechatVisitorDepartmentTransferParams } from '@rocket.chat/rest-typings/src/v1/omnichannel';
|
||||
import { check, Match } from 'meteor/check';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
import { API } from '../..';
|
||||
import { findGuest, findRoom, settings, findAgent, onCheckRoomParams } from './lib/livechat';
|
||||
import { canAccessRoomAsync } from '../../../lib/authorization';
|
||||
import { hasPermissionAsync } from '../../../lib/authorization/hasPermission';
|
||||
import { callbacks } from '../../../lib/callbacks';
|
||||
import { i18n } from '../../../lib/i18n';
|
||||
import { normalizeTransferredByData } from '../../../lib/omnichannel/Helper';
|
||||
import { closeLivechatRoom } from '../../../lib/omnichannel/closeLivechatRoom';
|
||||
import { closeRoom } from '../../../lib/omnichannel/closeRoom';
|
||||
import { saveGuest } from '../../../lib/omnichannel/guests';
|
||||
import type { CloseRoomParams } from '../../../lib/omnichannel/localTypes';
|
||||
import { livechatLogger } from '../../../lib/omnichannel/logger';
|
||||
import { createRoom, removeOmnichannelRoom, saveRoomInfo } from '../../../lib/omnichannel/rooms';
|
||||
import { transfer } from '../../../lib/omnichannel/transfer';
|
||||
import { addUserToRoom } from '../../../lib/rooms/addUserToRoom';
|
||||
import { settings as rcSettings } from '../../../settings';
|
||||
import type { ExtractRoutesFromAPI } from '../../ApiClass';
|
||||
import { isWidget } from '../../lib/isWidget';
|
||||
|
||||
const isAgentWithInfo = (agentObj: ILivechatAgent | { hiddenInfo: boolean }): agentObj is ILivechatAgent => !('hiddenInfo' in agentObj);
|
||||
|
||||
API.v1.addRoute(
|
||||
'livechat/room',
|
||||
{
|
||||
rateLimiterOptions: {
|
||||
numRequestsAllowed: 5,
|
||||
intervalTimeInMS: 60000,
|
||||
},
|
||||
},
|
||||
{
|
||||
async get() {
|
||||
// I'll temporary use check for validation, as validateParams doesnt support what's being done here
|
||||
const extraCheckParams = onCheckRoomParams({
|
||||
token: String,
|
||||
rid: Match.Maybe(String),
|
||||
agentId: Match.Maybe(String),
|
||||
});
|
||||
|
||||
check(this.queryParams, extraCheckParams);
|
||||
|
||||
const { token, rid, agentId, ...extraParams } = this.queryParams;
|
||||
|
||||
const guest = token && (await findGuest(token));
|
||||
if (!guest) {
|
||||
throw new Error('invalid-token');
|
||||
}
|
||||
|
||||
if (!rid) {
|
||||
const room = await LivechatRooms.findOneOpenByVisitorToken(token, {});
|
||||
if (room) {
|
||||
return API.v1.success({ room, newRoom: false });
|
||||
}
|
||||
|
||||
let agent: SelectedAgent | undefined;
|
||||
const agentObj = agentId && (await findAgent(agentId));
|
||||
if (agentObj) {
|
||||
if (isAgentWithInfo(agentObj)) {
|
||||
const { username = undefined } = agentObj;
|
||||
agent = { agentId, username };
|
||||
} else {
|
||||
agent = { agentId };
|
||||
}
|
||||
}
|
||||
|
||||
const roomInfo = {
|
||||
source: {
|
||||
...(isWidget(this.request.headers)
|
||||
? { type: OmnichannelSourceType.WIDGET, destination: this.request.headers.get('host')! }
|
||||
: { type: OmnichannelSourceType.API }),
|
||||
},
|
||||
};
|
||||
|
||||
const newRoom = await createRoom({
|
||||
visitor: guest,
|
||||
roomInfo,
|
||||
agent,
|
||||
extraData: extraParams as IOmnichannelInquiryExtraData,
|
||||
});
|
||||
|
||||
return API.v1.success({
|
||||
room: newRoom,
|
||||
newRoom: true,
|
||||
});
|
||||
}
|
||||
|
||||
const froom = await LivechatRooms.findOneOpenByRoomIdAndVisitorToken(rid, token, {});
|
||||
if (!froom) {
|
||||
throw new Error('invalid-room');
|
||||
}
|
||||
|
||||
return API.v1.success({ room: froom, newRoom: false });
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// Note: use this route if a visitor is closing a room
|
||||
// If a RC user(like eg agent) is closing a room, use the `livechat/room.closeByUser` route
|
||||
API.v1.addRoute(
|
||||
'livechat/room.close',
|
||||
{ validateParams: isPOSTLivechatRoomCloseParams },
|
||||
{
|
||||
async post() {
|
||||
const { rid, token } = this.bodyParams;
|
||||
|
||||
if (!rcSettings.get('Omnichannel_allow_visitors_to_close_conversation')) {
|
||||
throw new Error('error-not-allowed-to-close-conversation');
|
||||
}
|
||||
|
||||
const visitor = await findGuest(token);
|
||||
if (!visitor) {
|
||||
throw new Error('invalid-token');
|
||||
}
|
||||
|
||||
const room = await findRoom(token, rid);
|
||||
if (!room) {
|
||||
throw new Error('invalid-room');
|
||||
}
|
||||
|
||||
if (!room.open) {
|
||||
throw new Error('room-closed');
|
||||
}
|
||||
|
||||
const language = rcSettings.get<string>('Language') || 'en';
|
||||
const comment = i18n.t('Closed_by_visitor', { lng: language });
|
||||
|
||||
const options: CloseRoomParams['options'] = {};
|
||||
if (room.servedBy) {
|
||||
const servingAgent: Pick<IUser, '_id' | 'name' | 'username' | 'utcOffset' | 'settings' | 'language'> | null =
|
||||
await Users.findOneById(room.servedBy._id, {
|
||||
projection: {
|
||||
name: 1,
|
||||
username: 1,
|
||||
utcOffset: 1,
|
||||
settings: 1,
|
||||
language: 1,
|
||||
},
|
||||
});
|
||||
|
||||
if (servingAgent?.settings?.preferences?.omnichannelTranscriptPDF) {
|
||||
options.pdfTranscript = {
|
||||
requestedBy: servingAgent._id,
|
||||
};
|
||||
}
|
||||
|
||||
// We'll send the transcript by email only if the setting is disabled (that means, we're not asking the user if he wants to receive the transcript by email)
|
||||
// And the agent has the preference enabled to send the transcript by email and the visitor has an email address
|
||||
// When Livechat_enable_transcript is enabled, the email will be sent via livechat/transcript route
|
||||
if (
|
||||
!rcSettings.get<boolean>('Livechat_enable_transcript') &&
|
||||
servingAgent?.settings?.preferences?.omnichannelTranscriptEmail &&
|
||||
visitor.visitorEmails?.length &&
|
||||
visitor.visitorEmails?.[0]?.address
|
||||
) {
|
||||
const visitorEmail = visitor.visitorEmails?.[0]?.address;
|
||||
|
||||
const language = servingAgent.language || rcSettings.get<string>('Language') || 'en';
|
||||
const t = i18n.getFixedT(language);
|
||||
const subject = t('Transcript_of_your_livechat_conversation');
|
||||
|
||||
options.emailTranscript = {
|
||||
sendToVisitor: true,
|
||||
requestData: {
|
||||
email: visitorEmail,
|
||||
requestedAt: new Date(),
|
||||
requestedBy: servingAgent,
|
||||
subject,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
await closeRoom({ visitor, room, comment, options });
|
||||
|
||||
return API.v1.success({ rid, comment });
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
API.v1.addRoute(
|
||||
'livechat/room.closeByUser',
|
||||
{
|
||||
validateParams: isPOSTLivechatRoomCloseByUserParams,
|
||||
authRequired: true,
|
||||
permissionsRequired: ['close-livechat-room'],
|
||||
},
|
||||
{
|
||||
async post() {
|
||||
const { rid, comment, tags, generateTranscriptPdf, transcriptEmail, forceClose } = this.bodyParams;
|
||||
|
||||
const allowForceClose = rcSettings.get<boolean>('Omnichannel_allow_force_close_conversations');
|
||||
const isForceClosing = allowForceClose && forceClose;
|
||||
|
||||
if (isForceClosing) {
|
||||
livechatLogger.warn({ msg: 'Force closing a conversation', user: this.userId, room: rid });
|
||||
}
|
||||
|
||||
await closeLivechatRoom(this.user, rid, {
|
||||
comment,
|
||||
tags,
|
||||
generateTranscriptPdf,
|
||||
transcriptEmail,
|
||||
forceClose: isForceClosing,
|
||||
});
|
||||
|
||||
return API.v1.success();
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
API.v1.addRoute(
|
||||
'livechat/room.survey',
|
||||
{ validateParams: isPOSTLivechatRoomSurveyParams },
|
||||
{
|
||||
async post() {
|
||||
const { rid, token, data } = this.bodyParams;
|
||||
|
||||
const visitor = await findGuest(token);
|
||||
if (!visitor) {
|
||||
throw new Error('invalid-token');
|
||||
}
|
||||
|
||||
const room = await findRoom(token, rid);
|
||||
if (!room) {
|
||||
throw new Error('invalid-room');
|
||||
}
|
||||
|
||||
const config = await settings();
|
||||
if (!config.survey?.items || !config.survey.values) {
|
||||
throw new Error('invalid-livechat-config');
|
||||
}
|
||||
|
||||
const updateData: { [k: string]: string } = {};
|
||||
for (const item of data) {
|
||||
if ((config.survey.items.includes(item.name) && config.survey.values.includes(item.value)) || item.name === 'additionalFeedback') {
|
||||
updateData[item.name] = item.value;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(updateData).length === 0) {
|
||||
throw new Error('invalid-data');
|
||||
}
|
||||
|
||||
if (!(await LivechatRooms.updateSurveyFeedbackById(room._id, updateData))) {
|
||||
return API.v1.failure();
|
||||
}
|
||||
|
||||
return API.v1.success({ rid, data: updateData });
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
API.v1.addRoute(
|
||||
'livechat/room.forward',
|
||||
{ authRequired: true, permissionsRequired: ['view-l-room', 'transfer-livechat-guest'], validateParams: isLiveChatRoomForwardProps },
|
||||
{
|
||||
async post() {
|
||||
const transferData = this.bodyParams as typeof this.bodyParams & {
|
||||
transferredBy: TransferByData;
|
||||
transferredTo?: { _id: string; username?: string; name?: string };
|
||||
};
|
||||
|
||||
const room = await LivechatRooms.findOneById(this.bodyParams.roomId);
|
||||
if (room?.t !== 'l') {
|
||||
throw new Error('error-invalid-room');
|
||||
}
|
||||
|
||||
if (!room.open) {
|
||||
throw new Error('This_conversation_is_already_closed');
|
||||
}
|
||||
|
||||
if (!(await Omnichannel.isWithinMACLimit(room))) {
|
||||
throw new Error('error-mac-limit-reached');
|
||||
}
|
||||
|
||||
const guest = await LivechatVisitors.findOneEnabledById(room.v?._id);
|
||||
if (!guest) {
|
||||
throw new Error('error-invalid-visitor');
|
||||
}
|
||||
|
||||
transferData.transferredBy = normalizeTransferredByData(this.user, room);
|
||||
if (transferData.userId) {
|
||||
const userToTransfer = await Users.findOneById(transferData.userId);
|
||||
if (userToTransfer) {
|
||||
transferData.transferredTo = {
|
||||
_id: userToTransfer._id,
|
||||
username: userToTransfer.username,
|
||||
name: userToTransfer.name,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const chatForwardedResult = await transfer(room, guest, transferData);
|
||||
if (!chatForwardedResult) {
|
||||
throw new Error('error-forwarding-chat');
|
||||
}
|
||||
|
||||
return API.v1.success();
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const livechatVisitorDepartmentTransfer = API.v1.post(
|
||||
'livechat/visitor/department.transfer',
|
||||
{
|
||||
response: {
|
||||
200: ajv.compile<void>({
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
enum: [true],
|
||||
},
|
||||
},
|
||||
required: ['success'],
|
||||
additionalProperties: false,
|
||||
}),
|
||||
400: validateBadRequestErrorResponse,
|
||||
},
|
||||
body: isPOSTLivechatVisitorDepartmentTransferParams,
|
||||
},
|
||||
async function action() {
|
||||
const { rid, token, department } = this.bodyParams;
|
||||
|
||||
const visitor = await findGuest(token);
|
||||
if (!visitor) {
|
||||
return API.v1.failure('invalid-token');
|
||||
}
|
||||
const room = await LivechatRooms.findOneById(rid);
|
||||
|
||||
if (room?.t !== 'l') {
|
||||
return API.v1.failure('error-invalid-room');
|
||||
}
|
||||
|
||||
if (!room.open) {
|
||||
return API.v1.failure('This_conversation_is_already_closed');
|
||||
}
|
||||
|
||||
// As this is a visitor endpoint, we should not show the mac limit error
|
||||
if (!(await Omnichannel.isWithinMACLimit(room))) {
|
||||
return API.v1.failure('error-transefing-chat');
|
||||
}
|
||||
|
||||
const guest = await LivechatVisitors.findOneEnabledById(room.v?._id);
|
||||
if (!guest) {
|
||||
return API.v1.failure('error-invalid-visitor');
|
||||
}
|
||||
|
||||
const transferredBy = normalizeTransferredByData(
|
||||
{ _id: guest._id, username: guest.username, name: guest.name, userType: 'visitor' },
|
||||
room,
|
||||
);
|
||||
|
||||
const transferData: TransferData = { transferredBy, departmentId: department };
|
||||
|
||||
const chatForwardedResult = await transfer(room, guest, transferData);
|
||||
if (!chatForwardedResult) {
|
||||
return API.v1.failure('error-transfering-chat');
|
||||
}
|
||||
|
||||
return API.v1.success();
|
||||
},
|
||||
);
|
||||
|
||||
type LivechatAnalyticsEndpoints = ExtractRoutesFromAPI<typeof livechatVisitorDepartmentTransfer>;
|
||||
declare module '@rocket.chat/rest-typings' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
|
||||
interface Endpoints extends LivechatAnalyticsEndpoints {}
|
||||
}
|
||||
|
||||
API.v1.addRoute(
|
||||
'livechat/room.join',
|
||||
{ authRequired: true, permissionsRequired: ['view-l-room'], validateParams: isLiveChatRoomJoinProps },
|
||||
{
|
||||
async get() {
|
||||
const { roomId } = this.queryParams;
|
||||
|
||||
const { user } = this;
|
||||
|
||||
if (!user) {
|
||||
throw new Error('error-invalid-user');
|
||||
}
|
||||
|
||||
const room = await LivechatRooms.findOneById(roomId);
|
||||
|
||||
if (!room) {
|
||||
throw new Error('error-invalid-room');
|
||||
}
|
||||
|
||||
if (!room.open) {
|
||||
throw new Error('room-closed');
|
||||
}
|
||||
|
||||
if (!(await Omnichannel.isWithinMACLimit(room))) {
|
||||
throw new Error('error-mac-limit-reached');
|
||||
}
|
||||
|
||||
if (!(await canAccessRoomAsync(room, user))) {
|
||||
throw new Error('error-not-allowed');
|
||||
}
|
||||
|
||||
await addUserToRoom(roomId, user);
|
||||
|
||||
return API.v1.success();
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
API.v1.addRoute(
|
||||
'livechat/room.saveInfo',
|
||||
{ authRequired: true, permissionsRequired: ['view-l-room'], validateParams: isLiveChatRoomSaveInfoProps },
|
||||
{
|
||||
async post() {
|
||||
const { roomData, guestData } = this.bodyParams;
|
||||
const room = await LivechatRooms.findOneById(roomData._id);
|
||||
if (!room || !isOmnichannelRoom(room)) {
|
||||
throw new Error('error-invalid-room');
|
||||
}
|
||||
|
||||
if (
|
||||
(!room.servedBy || room.servedBy._id !== this.userId) &&
|
||||
!(await hasPermissionAsync(this.user, 'save-others-livechat-room-info'))
|
||||
) {
|
||||
return API.v1.forbidden();
|
||||
}
|
||||
|
||||
if (room.sms) {
|
||||
delete guestData.phone;
|
||||
}
|
||||
|
||||
// We want this both operations to be concurrent, so we have to go with Promise.allSettled
|
||||
const result = await Promise.allSettled([saveGuest(guestData, this.userId), saveRoomInfo(roomData)]);
|
||||
|
||||
const firstError = result.find((item) => item.status === 'rejected');
|
||||
if (firstError) {
|
||||
throw new Error(firstError.reason.error);
|
||||
}
|
||||
|
||||
await callbacks.run('livechat.saveInfo', await LivechatRooms.findOneById(roomData._id), {
|
||||
user: this.user,
|
||||
oldRoom: room,
|
||||
});
|
||||
|
||||
return API.v1.success();
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const livechatRoomsEndpoints = API.v1
|
||||
.post(
|
||||
'livechat/rooms.delete',
|
||||
{
|
||||
response: {
|
||||
200: POSTLivechatRemoveRoomSuccess,
|
||||
400: validateBadRequestErrorResponse,
|
||||
401: validateUnauthorizedErrorResponse,
|
||||
403: validateForbiddenErrorResponse,
|
||||
},
|
||||
authRequired: true,
|
||||
permissionsRequired: ['remove-closed-livechat-room'],
|
||||
body: isPOSTLivechatRemoveRoomParams,
|
||||
},
|
||||
async function action() {
|
||||
const { roomId } = this.bodyParams;
|
||||
|
||||
try {
|
||||
await removeOmnichannelRoom(roomId);
|
||||
return API.v1.success();
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Meteor.Error) {
|
||||
return API.v1.failure(error.reason);
|
||||
}
|
||||
|
||||
return API.v1.failure('error-removing-room');
|
||||
}
|
||||
},
|
||||
)
|
||||
.post(
|
||||
'livechat/rooms.removeAllClosedRooms',
|
||||
{
|
||||
response: {
|
||||
200: isPOSTLivechatRoomsCloseAllSuccessResponse,
|
||||
},
|
||||
authRequired: true,
|
||||
permissionsRequired: ['remove-closed-livechat-rooms'],
|
||||
body: isPOSTLivechatRoomsCloseAll,
|
||||
},
|
||||
async function action() {
|
||||
livechatLogger.info({ msg: 'User is removing all closed rooms', userId: this.userId });
|
||||
|
||||
const params = this.bodyParams;
|
||||
|
||||
const extraQuery = await callbacks.run('livechat.applyRoomRestrictions', {}, { userId: this.userId });
|
||||
const promises: Promise<void>[] = [];
|
||||
await LivechatRooms.findClosedRooms(params?.departmentIds, {}, extraQuery).forEach(({ _id }: IOmnichannelRoom) => {
|
||||
promises.push(removeOmnichannelRoom(_id));
|
||||
});
|
||||
await Promise.all(promises);
|
||||
|
||||
livechatLogger.info({ msg: 'User removed closed rooms', userId: this.userId, removedRooms: promises.length });
|
||||
return API.v1.success({ removedRooms: promises.length });
|
||||
},
|
||||
);
|
||||
|
||||
type LivechatRoomsEndpoints = ExtractRoutesFromAPI<typeof livechatRoomsEndpoints>;
|
||||
|
||||
declare module '@rocket.chat/rest-typings' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
|
||||
interface Endpoints extends LivechatRoomsEndpoints {}
|
||||
}
|
||||
Reference in New Issue
Block a user