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

[NEW] [Apps-Engine] New Room events #17487

Merged
merged 16 commits into from May 19, 2020
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/apps/server/bridges/internal.js
Expand Up @@ -6,6 +6,10 @@ export class AppInternalBridge {
}

getUsernamesOfRoomById(roomId) {
if (!roomId) {
return [];
}

const records = Subscriptions.findByRoomIdWhenUsernameExists(roomId, {
fields: {
'u.username': 1,
Expand Down
97 changes: 65 additions & 32 deletions app/apps/server/bridges/listeners.js
Expand Up @@ -5,6 +5,49 @@ export class AppListenerBridge {
this.orch = orch;
}

async handleEvent(event, ...payload) {
const method = (() => {
switch (event) {
case AppInterface.IPreMessageSentPrevent:
case AppInterface.IPreMessageSentExtend:
case AppInterface.IPreMessageSentModify:
case AppInterface.IPostMessageSent:
case AppInterface.IPreMessageDeletePrevent:
case AppInterface.IPostMessageDeleted:
case AppInterface.IPreMessageUpdatedPrevent:
case AppInterface.IPreMessageUpdatedExtend:
case AppInterface.IPreMessageUpdatedModify:
case AppInterface.IPostMessageUpdated:
return 'messageEvent';
case AppInterface.IPreRoomCreatePrevent:
case AppInterface.IPreRoomCreateExtend:
case AppInterface.IPreRoomCreateModify:
case AppInterface.IPostRoomCreate:
case AppInterface.IPreRoomDeletePrevent:
case AppInterface.IPostRoomDeleted:
case AppInterface.IPreRoomUserJoined:
case AppInterface.IPostRoomUserJoined:
return 'roomEvent';
case AppInterface.IPostExternalComponentOpened:
case AppInterface.IPostExternalComponentClosed:
return 'externalComponentEvent';
/**
* @deprecated please prefer the AppInterface.IPostLivechatRoomClosed event
*/
case AppInterface.ILivechatRoomClosedHandler:
case AppInterface.IPostLivechatRoomStarted:
case AppInterface.IPostLivechatRoomClosed:
case AppInterface.IPostLivechatAgentAssigned:
case AppInterface.IPostLivechatAgentUnassigned:
return 'livechatEvent';
case AppInterface.IUIKitInteractionHandler:
return 'uiKitInteractionEvent';
}
})();

return this[method](event, ...payload);
}

async messageEvent(inte, message) {
const msg = this.orch.getConverters().get('messages').convertMessage(message);
const result = await this.orch.getManager().getListenerManager().executeListener(inte, msg);
Expand All @@ -13,64 +56,54 @@ export class AppListenerBridge {
return result;
}
return this.orch.getConverters().get('messages').convertAppMessage(result);

// try {

// } catch (e) {
// this.orch.debugLog(`${ e.name }: ${ e.message }`);
// this.orch.debugLog(e.stack);
// }
}

async roomEvent(inte, room) {
async roomEvent(inte, room, ...payload) {
const rm = this.orch.getConverters().get('rooms').convertRoom(room);
const result = await this.orch.getManager().getListenerManager().executeListener(inte, rm);

const params = (() => {
switch (inte) {
case AppInterface.IPreRoomUserJoined:
case AppInterface.IPostRoomUserJoined:
const [joiningUser, invitingUser] = payload;
return {
room: rm,
joiningUser: this.orch.getConverters().get('users').convertToApp(joiningUser),
invitingUser: this.orch.getConverters().get('users').convertToApp(invitingUser),
};
default:
return rm;
}
})();

const result = await this.orch.getManager().getListenerManager().executeListener(inte, params);

if (typeof result === 'boolean') {
return result;
}
return this.orch.getConverters().get('rooms').convertAppRoom(result);

// try {

// } catch (e) {
// this.orch.debugLog(`${ e.name }: ${ e.message }`);
// this.orch.debugLog(e.stack);
// }
}

async externalComponentEvent(inte, externalComponent) {
const result = await this.orch.getManager().getListenerManager().executeListener(inte, externalComponent);

return result;
return this.orch.getManager().getListenerManager().executeListener(inte, externalComponent);
}

async uiKitInteractionEvent(inte, action) {
return this.orch.getManager().getListenerManager().executeListener(inte, action);

// try {

// } catch (e) {
// this.orch.debugLog(`${ e.name }: ${ e.message }`);
// this.orch.debugLog(e.stack);
// }
}

async livechatEvent(inte, data) {
switch (inte) {
case AppInterface.IPostLivechatRoomStarted:
case AppInterface.IPostLivechatRoomClosed:
const room = this.orch.getConverters().get('rooms').convertRoom(data);

return this.orch.getManager().getListenerManager().executeListener(inte, room);
case AppInterface.IPostLivechatAgentAssigned:
case AppInterface.IPostLivechatAgentUnassigned:
return this.orch.getManager().getListenerManager().executeListener(inte, {
room: this.orch.getConverters().get('rooms').convertRoom(data.room),
agent: this.orch.getConverters().get('users').convertToApp(data.user),
});
default:
break;
const room = this.orch.getConverters().get('rooms').convertRoom(data);

return this.orch.getManager().getListenerManager().executeListener(inte, room);
}
}
}
1 change: 1 addition & 0 deletions app/apps/server/converters/rooms.js
Expand Up @@ -116,6 +116,7 @@ export class AppRoomsConverter {
customFields: 'customFields',
isWaitingResponse: 'waitingResponse',
isOpen: 'open',
_USERNAMES: '_USERNAMES',
isDefault: (room) => {
const result = !!room.default;
delete room.default;
Expand Down
2 changes: 1 addition & 1 deletion app/apps/server/index.js
@@ -1,3 +1,3 @@
import './cron';

export { Apps } from './orchestrator';
export { Apps, AppEvents } from './orchestrator';
10 changes: 10 additions & 0 deletions app/apps/server/orchestrator.js
@@ -1,5 +1,6 @@
import { Meteor } from 'meteor/meteor';
import { AppManager } from '@rocket.chat/apps-engine/server/AppManager';
import { AppInterface } from '@rocket.chat/apps-engine/server/compiler';

import { Logger } from '../../logger';
import { AppsLogsModel, AppsModel, AppsPersistenceModel, Permissions } from '../../models';
Expand Down Expand Up @@ -155,8 +156,17 @@ class AppServerOrchestrator {
return this._manager.updateAppsMarketplaceInfo(apps)
.then(() => this._manager.get());
}

async triggerEvent(event, ...payload) {
if (!this.isLoaded()) {
return;
}

return this.getBridges().getListenerBridge().handleEvent(event, ...payload);
}
}

export const AppEvents = AppInterface;
export const Apps = new AppServerOrchestrator();

settings.addGroup('General', function() {
Expand Down
18 changes: 16 additions & 2 deletions app/lib/server/functions/addUserToRoom.js
@@ -1,8 +1,10 @@
import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions';
import { Meteor } from 'meteor/meteor';

import { Rooms, Subscriptions, Messages } from '../../../models';
import { AppEvents, Apps } from '../../../apps/server';
import { callbacks } from '../../../callbacks';
import { roomTypes, RoomMemberActions } from '../../../utils/server';
import { Messages, Rooms, Subscriptions } from '../../../models';
import { RoomMemberActions, roomTypes } from '../../../utils/server';

export const addUserToRoom = function(rid, user, inviter, silenced) {
const now = new Date();
Expand All @@ -27,6 +29,16 @@ export const addUserToRoom = function(rid, user, inviter, silenced) {
callbacks.run('beforeJoinRoom', user, room);
}

try {
rodrigok marked this conversation as resolved.
Show resolved Hide resolved
Promise.await(Apps.triggerEvent(AppEvents.IPreRoomUserJoined, room, user, inviter));
} catch (error) {
if (error instanceof AppsEngineException) {
throw new Meteor.Error('error-app-prevented', error.message);
}

throw error;
}

Subscriptions.createWithRoomAndUser(room, user, {
ts: now,
open: true,
Expand Down Expand Up @@ -59,6 +71,8 @@ export const addUserToRoom = function(rid, user, inviter, silenced) {

// Keep the current event
callbacks.run('afterJoinRoom', user, room);

Apps.triggerEvent(AppEvents.IPostRoomUserJoined, room, user, inviter);
});
}

Expand Down
32 changes: 29 additions & 3 deletions app/lib/server/functions/createDirectRoom.js
@@ -1,7 +1,10 @@
import { Meteor } from 'meteor/meteor';

import { callbacks } from '../../../callbacks/server';
import { Rooms, Subscriptions } from '../../../models/server';
import { settings } from '../../../settings/server';
import { getDefaultSubscriptionPref } from '../../../utils/server';
import { callbacks } from '../../../callbacks/server';
import { Apps } from '../../../apps/server';

const generateSubscription = (fname, name, user, extra) => ({
alert: false,
Expand Down Expand Up @@ -40,7 +43,7 @@ export const createDirectRoom = function(members, roomExtraData = {}, options =

const isNewRoom = !room;

const rid = room?._id || Rooms.insert({
const roomInfo = {
...uids.length === 2 && { _id: uids.join('') }, // Deprecated: using users' _id to compose the room _id is deprecated
t: 'd',
usernames,
Expand All @@ -49,7 +52,28 @@ export const createDirectRoom = function(members, roomExtraData = {}, options =
ts: new Date(),
uids,
...roomExtraData,
});
};

if (isNewRoom) {
roomInfo._USERNAMES = usernames;

const prevent = Promise.await(Apps.triggerEvent('IPreRoomCreatePrevent', roomInfo));
if (prevent) {
throw new Meteor.Error('error-app-prevented-creation', 'A Rocket.Chat App prevented the room creation.');
}

let result;
result = Promise.await(Apps.triggerEvent('IPreRoomCreateExtend', roomInfo));
result = Promise.await(Apps.triggerEvent('IPreRoomCreateModify', result));

if (typeof result === 'object') {
Object.assign(roomInfo, result);
}

delete roomInfo._USERNAMES;
}

const rid = room?._id || Rooms.insert(roomInfo);

if (members.length === 1) { // dm to yourself
Subscriptions.upsert({ rid, 'u._id': members[0]._id }, {
Expand Down Expand Up @@ -80,6 +104,8 @@ export const createDirectRoom = function(members, roomExtraData = {}, options =
const insertedRoom = Rooms.findOneById(rid);

callbacks.run('afterCreateDirectRoom', insertedRoom, { members });

Apps.triggerEvent('IPostRoomCreate', insertedRoom);
}

return {
Expand Down
30 changes: 14 additions & 16 deletions app/lib/server/functions/createRoom.js
Expand Up @@ -62,21 +62,23 @@ export const createRoom = function(type, name, owner, members = [], readOnly, ex
ro: readOnly === true,
};

if (Apps && Apps.isLoaded()) {
const prevent = Promise.await(Apps.getBridges().getListenerBridge().roomEvent('IPreRoomCreatePrevent', room));
if (prevent) {
throw new Meteor.Error('error-app-prevented-creation', 'A Rocket.Chat App prevented the room creation.');
}
room._USERNAMES = members;

let result;
result = Promise.await(Apps.getBridges().getListenerBridge().roomEvent('IPreRoomCreateExtend', room));
result = Promise.await(Apps.getBridges().getListenerBridge().roomEvent('IPreRoomCreateModify', result));
const prevent = Promise.await(Apps.triggerEvent('IPreRoomCreatePrevent', room));
if (prevent) {
throw new Meteor.Error('error-app-prevented-creation', 'A Rocket.Chat App prevented the room creation.');
}

if (typeof result === 'object') {
room = Object.assign(room, result);
}
let result;
result = Promise.await(Apps.triggerEvent('IPreRoomCreateExtend', room));
result = Promise.await(Apps.triggerEvent('IPreRoomCreateModify', result));

if (typeof result === 'object') {
Object.assign(room, result);
}

delete room._USERNAMES;

if (type === 'c') {
callbacks.run('beforeCreateChannel', owner, room);
}
Expand Down Expand Up @@ -119,11 +121,7 @@ export const createRoom = function(type, name, owner, members = [], readOnly, ex
callbacks.run('afterCreateRoom', owner, room);
});

if (Apps && Apps.isLoaded()) {
// This returns a promise, but it won't mutate anything about the message
// so, we don't really care if it is successful or fails
Apps.getBridges().getListenerBridge().roomEvent('IPostRoomCreate', room);
}
Apps.triggerEvent('IPostRoomCreate', room);

return {
rid: room._id, // backwards compatible
Expand Down