Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
73dfc92
feat(apps-engine): adds executeLivechatRoomCreatePrevent event
alfredodelfabro Mar 13, 2025
1a77c8d
Merge remote-tracking branch 'upstream/develop' into feat/pre-room-cr…
alfredodelfabro Mar 13, 2025
22055a1
fix: add LivechatRoom info to executePreLivechatRoomCreatePrevent
alfredodelfabro Mar 14, 2025
a8c0f0b
fix: simplifies hook logic
alfredodelfabro Mar 17, 2025
992cf13
fix: keeps default error message when returning a boolean from apps
alfredodelfabro Mar 17, 2025
757f45f
refactor: removes boolean return from apps engine hook
alfredodelfabro Mar 18, 2025
0cadb99
Merge branch 'develop' into feat/pre-room-create-prevent-livechat
alfredodelfabro Mar 18, 2025
d1d9463
chore: added just for CI debug
alfredodelfabro Mar 18, 2025
6388d8e
Merge branch 'develop' into feat/pre-room-create-prevent-livechat
alfredodelfabro Mar 18, 2025
570947f
fix: lint
alfredodelfabro Mar 18, 2025
ef2df38
Merge branch 'develop' into feat/pre-room-create-prevent-livechat
alfredodelfabro Mar 19, 2025
312b03b
fix: install test app before runs testapi
alfredodelfabro Mar 19, 2025
6a6d6d1
Merge branch 'develop' into feat/pre-room-create-prevent-livechat
alfredodelfabro Mar 19, 2025
b04a625
fix: lint
alfredodelfabro Mar 19, 2025
c9000c4
Merge branch 'develop' into feat/pre-room-create-prevent-livechat
alfredodelfabro Mar 19, 2025
ea1100e
test: install app for API tests
sampaiodiego Mar 19, 2025
cac913b
Update packages/apps-engine/src/definition/livechat/IPreLivechatRoomC…
d-gubert Mar 19, 2025
58d51bc
chore: adds changeset
alfredodelfabro Mar 19, 2025
c16b0c5
Merge branch 'develop' into feat/pre-room-create-prevent-livechat
d-gubert Mar 19, 2025
3ebbddb
test: check if app is enabled
sampaiodiego Mar 19, 2025
c3b0197
test: unify APP_URL constant
sampaiodiego Mar 19, 2025
e734fa3
test: run test only on EE
sampaiodiego Mar 19, 2025
4302d5d
Merge remote-tracking branch 'origin/develop' into feat/pre-room-crea…
sampaiodiego Mar 20, 2025
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
7 changes: 7 additions & 0 deletions .changeset/sour-ghosts-count.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@rocket.chat/apps-engine': minor
'@rocket.chat/apps': minor
'@rocket.chat/meteor': minor
---

Adds the executeLivechatRoomCreatePrevent hook to the Rocket.Chat Apps-Engine to prevent the creation of live chat rooms.
1 change: 1 addition & 0 deletions apps/meteor/app/apps/server/bridges/listeners.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export class AppListenerBridge {
* @deprecated please prefer the AppInterface.IPostLivechatRoomClosed event
*/
case AppInterface.ILivechatRoomClosedHandler:
case AppInterface.IPreLivechatRoomCreatePrevent:
case AppInterface.IPostLivechatRoomStarted:
case AppInterface.IPostLivechatRoomClosed:
case AppInterface.IPostLivechatAgentAssigned:
Expand Down
11 changes: 11 additions & 0 deletions apps/meteor/app/livechat/server/lib/QueueManager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Apps, AppEvents } from '@rocket.chat/apps';
import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions/AppsEngineException';
import { Message, Omnichannel } from '@rocket.chat/core-services';
import type {
ILivechatDepartment,
Expand Down Expand Up @@ -337,6 +338,16 @@ export class QueueManager {
...(Boolean(customFields) && { customFields }),
});

try {
await Apps.self?.triggerEvent(AppEvents.IPreLivechatRoomCreatePrevent, insertionRoom);
} catch (error: any) {
if (error.name === AppsEngineException.name) {
throw new Meteor.Error('error-app-prevented', error.message);
}

throw error;
}

// Transactional start of the conversation. This should prevent rooms from being created without inquiries and viceversa.
// All the actions that happened inside createLivechatRoom are now outside this transaction
const { room, inquiry } = await this.startConversation(rid, insertionRoom, guest, roomInfo, defaultAgent, message, extraData);
Expand Down
37 changes: 37 additions & 0 deletions apps/meteor/tests/end-to-end/api/livechat/00-rooms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type { Response } from 'supertest';

import type { SuccessResult } from '../../../../app/api/server/definition';
import { getCredentials, api, request, credentials, methodCall } from '../../../data/api-data';
import { apps, APP_URL } from '../../../data/apps/apps-data';
import { createCustomField } from '../../../data/livechat/custom-fields';
import { createDepartmentWithAnOfflineAgent, createDepartmentWithAnOnlineAgent, deleteDepartment } from '../../../data/livechat/department';
import { createSLA, getRandomPriority } from '../../../data/livechat/priorities';
Expand Down Expand Up @@ -73,21 +74,50 @@ const getSubscriptionForRoom = async (roomId: string, overrideCredential?: Crede
describe('LIVECHAT - rooms', () => {
let visitor: ILivechatVisitor;
let room: IOmnichannelRoom;
let appId: string;

before((done) => getCredentials(done));

before(async () => {
if (IS_EE) {
// install the app
await request
.post(apps('/'))
.set(credentials)
.send({ url: APP_URL })
.expect('Content-Type', 'application/json')
.expect(200)
.expect((res: Response) => {
expect(res.body).to.have.a.property('success', true);
expect(res.body).to.have.a.property('app');
expect(res.body.app).to.have.a.property('id');
expect(res.body.app).to.have.a.property('version');
expect(res.body.app).to.have.a.property('status').and.to.be.equal('auto_enabled');

appId = res.body.app.id;
});
}

await updateSetting('Livechat_enabled', true);
await updateEESetting('Livechat_Require_Contact_Verification', 'never');
await updateSetting('Omnichannel_enable_department_removal', true);
await createAgent();
await makeAgentAvailable();

visitor = await createVisitor();

room = await createLivechatRoom(visitor.token);
});

after(async () => {
await updateSetting('Omnichannel_enable_department_removal', false);

if (IS_EE) {
await request
.delete(apps(`/${appId}`))
.set(credentials)
.expect(200);
}
});

describe('livechat/room', () => {
Expand All @@ -101,6 +131,13 @@ describe('LIVECHAT - rooms', () => {
const visitor = await createVisitor();
await request.get(api('livechat/room')).query({ token: visitor.token, rid: 'invalid-rid' }).expect(400);
});
(IS_EE ? it : it.skip)('should prevent create a room for visitor if an app throws an error', async () => {
// this test relies on the app installed by the insertApp fixture
const visitor = await createVisitor(undefined, 'visitor prevent from app');
const { body } = await request.get(api('livechat/room')).query({ token: visitor.token });

expect(body).to.have.property('success', false);
});
it('should create a room for visitor', async () => {
const visitor = await createVisitor();
const { body } = await request.get(api('livechat/room')).query({ token: visitor.token });
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { IRead, IHttp, IPersistence } from "../accessors";
import { AppMethod } from "../metadata";
import { ILivechatRoom } from "./ILivechatRoom";

/**
* Handler called before a livechat room is created.
*
* To prevent the room from being created, the app should throw an `AppsEngineException`
*/
export interface IPreLivechatRoomCreatePrevent {
/**
* Method called *before* a livechat room is created.
*
* @param livechatRoom The livechat room which is about to be created
* @param read An accessor to the environment
* @param http An accessor to the outside world
* @param persis An accessor to the App's persistence
* @param modify An accessor to the modifier
*/
[AppMethod.EXECUTE_PRE_LIVECHAT_ROOM_CREATE_PREVENT](
room: ILivechatRoom,
read: IRead,
http: IHttp,
persis: IPersistence
): Promise<void>;
}
2 changes: 2 additions & 0 deletions packages/apps-engine/src/definition/livechat/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { IPostLivechatRoomClosed } from './IPostLivechatRoomClosed';
import { IPostLivechatRoomSaved } from './IPostLivechatRoomSaved';
import { IPostLivechatRoomStarted } from './IPostLivechatRoomStarted';
import { IPostLivechatRoomTransferred } from './IPostLivechatRoomTransferred';
import { IPreLivechatRoomCreatePrevent } from './IPreLivechatRoomCreatePrevent';
import { IVisitor } from './IVisitor';
import { IVisitorEmail } from './IVisitorEmail';
import { IVisitorPhone } from './IVisitorPhone';
Expand All @@ -22,6 +23,7 @@ export {
ILivechatMessage,
ILivechatRoom,
IPostLivechatAgentAssigned,
IPreLivechatRoomCreatePrevent,
ILivechatContact,
IPostLivechatAgentUnassigned,
IPostLivechatGuestSaved,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export enum AppInterface {
* @deprecated please use the AppMethod.EXECUTE_POST_LIVECHAT_ROOM_CLOSED method
*/
ILivechatRoomClosedHandler = 'ILivechatRoomClosedHandler',
IPreLivechatRoomCreatePrevent = "IPreLivechatRoomCreatePrevent",
IPostLivechatAgentAssigned = 'IPostLivechatAgentAssigned',
IPostLivechatAgentUnassigned = 'IPostLivechatAgentUnassigned',
IPostLivechatRoomTransferred = 'IPostLivechatRoomTransferred',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export enum AppMethod {
* @deprecated please use the AppMethod.EXECUTE_POST_LIVECHAT_ROOM_CLOSED method
*/
EXECUTE_LIVECHAT_ROOM_CLOSED_HANDLER = 'executeLivechatRoomClosedHandler',
EXECUTE_PRE_LIVECHAT_ROOM_CREATE_PREVENT = 'executeLivechatRoomCreatePrevent',
EXECUTE_POST_LIVECHAT_ROOM_CLOSED = 'executePostLivechatRoomClosed',
EXECUTE_POST_LIVECHAT_AGENT_ASSIGNED = 'executePostLivechatAgentAssigned',
EXECUTE_POST_LIVECHAT_AGENT_UNASSIGNED = 'executePostLivechatAgentUnassigned',
Expand Down
14 changes: 14 additions & 0 deletions packages/apps-engine/src/server/managers/AppListenerManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ interface IListenerExecutor {
args: [ILivechatRoom];
result: void;
};
[AppInterface.IPreLivechatRoomCreatePrevent]: {
args: [ILivechatRoom];
result: void;
};
[AppInterface.IPostLivechatRoomClosed]: {
args: [ILivechatRoom];
result: void;
Expand Down Expand Up @@ -411,6 +415,8 @@ export class AppListenerManager {
*/
case AppInterface.ILivechatRoomClosedHandler:
return this.executeLivechatRoomClosedHandler(data as ILivechatRoom);
case AppInterface.IPreLivechatRoomCreatePrevent:
return this.executePreLivechatRoomCreatePrevent(data as ILivechatRoom);
case AppInterface.IPostLivechatRoomClosed:
return this.executePostLivechatRoomClosed(data as ILivechatRoom);
case AppInterface.IPostLivechatRoomSaved:
Expand Down Expand Up @@ -1058,6 +1064,14 @@ export class AppListenerManager {
}

// Livechat
private async executePreLivechatRoomCreatePrevent(data: ILivechatRoom): Promise<void> {
for (const appId of this.listeners.get(AppInterface.IPreLivechatRoomCreatePrevent)) {
const app = this.manager.getOneById(appId);

await app.call(AppMethod.EXECUTE_PRE_LIVECHAT_ROOM_CREATE_PREVENT, data);
}
}

private async executePostLivechatRoomStarted(data: ILivechatRoom): Promise<void> {
for (const appId of this.listeners.get(AppInterface.IPostLivechatRoomStarted)) {
const app = this.manager.getOneById(appId);
Expand Down
5 changes: 4 additions & 1 deletion packages/apps/src/bridges/IListenerBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ declare module '@rocket.chat/apps-engine/server/bridges' {
): Promise<void>;
livechatEvent(int: 'IPostLivechatGuestSaved', data: ILivechatVisitor['_id']): Promise<void>;
livechatEvent(int: 'IPostLivechatRoomSaved', data: IRoom['_id']): Promise<void>;
livechatEvent(int: 'ILivechatRoomClosedHandler' | 'IPostLivechatRoomStarted' | 'IPostLivechatRoomClosed', data: IRoom): Promise<void>;
livechatEvent(
int: 'ILivechatRoomClosedHandler' | 'IPostLivechatRoomStarted' | 'IPostLivechatRoomClosed' | 'IPreLivechatRoomCreatePrevent',
data: IRoom,
): Promise<void>;
livechatEvent(int: AppEvents | AppEvents[keyof AppEvents], data: any): Promise<void>;
}
}
Loading