From a6802cacafa37a9e40e22ca7af1bf898a46239dc Mon Sep 17 00:00:00 2001 From: Pierre Lehnen <55164754+pierre-lehnen-rc@users.noreply.github.com> Date: Thu, 7 Mar 2024 00:50:54 -0300 Subject: [PATCH] chore: Remove references to EE code from the app events (#31775) Co-authored-by: Guilherme Gazzo --- .../authentication/server/startup/index.js | 8 +-- .../app/file-upload/server/lib/FileUpload.ts | 4 +- .../app/lib/server/functions/addUserToRoom.ts | 6 +- .../lib/server/functions/createDirectRoom.ts | 11 ++-- .../app/lib/server/functions/createRoom.ts | 11 ++-- .../app/lib/server/functions/deleteMessage.ts | 10 ++-- .../server/functions/removeUserFromRoom.ts | 6 +- .../app/lib/server/functions/saveUser.js | 4 +- .../app/lib/server/functions/sendMessage.ts | 2 +- .../app/lib/server/functions/updateMessage.ts | 35 +++++------ .../server/methods/deleteUserOwnAccount.ts | 4 +- apps/meteor/app/livechat/server/lib/Helper.ts | 10 ++-- .../app/livechat/server/lib/LivechatTyped.ts | 10 ++-- .../app/livechat/server/lib/RoutingManager.ts | 4 +- apps/meteor/app/mailer/server/api.ts | 4 +- .../app/message-pin/server/pinMessage.ts | 6 +- .../app/message-star/server/starMessage.ts | 4 +- .../app/reactions/server/setReaction.ts | 4 +- .../server/lib/getAppsStatistics.js | 15 ++--- .../threads/server/methods/followMessage.ts | 4 +- .../threads/server/methods/unfollowMessage.ts | 4 +- apps/meteor/ee/server/apps/index.ts | 2 +- apps/meteor/ee/server/apps/orchestrator.js | 4 +- .../server/lib/moderation/reportMessage.ts | 4 +- apps/meteor/server/methods/deleteUser.ts | 4 +- apps/meteor/server/methods/eraseRoom.ts | 6 +- apps/meteor/server/methods/logoutCleanUp.ts | 4 +- apps/meteor/server/methods/reportMessage.ts | 4 +- apps/meteor/server/methods/saveUserProfile.ts | 4 +- .../server/services/apps-engine/service.ts | 58 ++++++++++--------- .../services/video-conference/service.ts | 4 +- apps/meteor/server/startup/migrations/v291.ts | 11 ++-- apps/meteor/server/startup/migrations/v292.ts | 6 +- apps/meteor/server/startup/migrations/v294.ts | 6 +- packages/apps/src/AppsEngine.ts | 3 + packages/apps/src/IAppServerOrchestrator.ts | 8 +++ packages/apps/src/bridges/IListenerBridge.ts | 48 +++++++++++++++ packages/apps/src/index.ts | 3 + packages/apps/src/orchestrator.ts | 7 +++ yarn.lock | 4 +- 40 files changed, 223 insertions(+), 133 deletions(-) create mode 100644 packages/apps/src/bridges/IListenerBridge.ts create mode 100644 packages/apps/src/orchestrator.ts diff --git a/apps/meteor/app/authentication/server/startup/index.js b/apps/meteor/app/authentication/server/startup/index.js index e3b97c1aae8..ab622be95d5 100644 --- a/apps/meteor/app/authentication/server/startup/index.js +++ b/apps/meteor/app/authentication/server/startup/index.js @@ -1,3 +1,4 @@ +import { Apps, AppEvents } from '@rocket.chat/apps'; import { Roles, Settings, Users } from '@rocket.chat/models'; import { escapeRegExp, escapeHTML } from '@rocket.chat/string-helpers'; import { Accounts } from 'meteor/accounts-base'; @@ -5,7 +6,6 @@ import { Match } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; -import { AppEvents, Apps } from '../../../../ee/server/apps/orchestrator'; import { callbacks } from '../../../../lib/callbacks'; import { beforeCreateUserCallback } from '../../../../lib/callbacks/beforeCreateUserCallback'; import { parseCSV } from '../../../../lib/utils/parseCSV'; @@ -350,8 +350,8 @@ const insertUserDocAsync = async function (options, user) { if (!options.skipAppsEngineEvent) { // `post` triggered events don't need to wait for the promise to resolve - Apps.triggerEvent(AppEvents.IPostUserCreated, { user, performedBy: await safeGetMeteorUser() }).catch((e) => { - Apps.getRocketChatLogger().error('Error while executing post user created event:', e); + Apps?.triggerEvent(AppEvents.IPostUserCreated, { user, performedBy: await safeGetMeteorUser() }).catch((e) => { + Apps?.getRocketChatLogger().error('Error while executing post user created event:', e); }); } @@ -424,7 +424,7 @@ const validateLoginAttemptAsync = async function (login) { */ if (login.type !== 'resume') { // App IPostUserLoggedIn event hook - await Apps.triggerEvent(AppEvents.IPostUserLoggedIn, login.user); + await Apps?.triggerEvent(AppEvents.IPostUserLoggedIn, login.user); } return true; diff --git a/apps/meteor/app/file-upload/server/lib/FileUpload.ts b/apps/meteor/app/file-upload/server/lib/FileUpload.ts index e512e5d09bf..342d541f01b 100644 --- a/apps/meteor/app/file-upload/server/lib/FileUpload.ts +++ b/apps/meteor/app/file-upload/server/lib/FileUpload.ts @@ -8,6 +8,7 @@ import stream from 'stream'; import URL from 'url'; import { hashLoginToken } from '@rocket.chat/account-utils'; +import { Apps, AppEvents } from '@rocket.chat/apps'; import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions'; import type { IUpload } from '@rocket.chat/core-typings'; import { Users, Avatars, UserDataFiles, Uploads, Settings, Subscriptions, Messages, Rooms } from '@rocket.chat/models'; @@ -21,7 +22,6 @@ import sharp from 'sharp'; import type { WritableStreamBuffer } from 'stream-buffers'; import streamBuffers from 'stream-buffers'; -import { AppEvents, Apps } from '../../../../ee/server/apps'; import { i18n } from '../../../../server/lib/i18n'; import { SystemLogger } from '../../../../server/lib/logger/system'; import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; @@ -177,7 +177,7 @@ export const FileUpload = { // App IPreFileUpload event hook try { - await Apps.triggerEvent(AppEvents.IPreFileUpload, { file, content: content || Buffer.from([]) }); + await Apps?.triggerEvent(AppEvents.IPreFileUpload, { file, content: content || Buffer.from([]) }); } catch (error: any) { if (error.name === AppsEngineException.name) { throw new Meteor.Error('error-app-prevented', error.message); diff --git a/apps/meteor/app/lib/server/functions/addUserToRoom.ts b/apps/meteor/app/lib/server/functions/addUserToRoom.ts index 4e29576cf3b..4a70943d28e 100644 --- a/apps/meteor/app/lib/server/functions/addUserToRoom.ts +++ b/apps/meteor/app/lib/server/functions/addUserToRoom.ts @@ -1,3 +1,4 @@ +import { Apps, AppEvents } from '@rocket.chat/apps'; import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions'; import { Message, Team } from '@rocket.chat/core-services'; import type { IUser } from '@rocket.chat/core-typings'; @@ -5,7 +6,6 @@ import { Subscriptions, Users, Rooms } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; import { RoomMemberActions } from '../../../../definition/IRoomTypeConfig'; -import { AppEvents, Apps } from '../../../../ee/server/apps'; import { callbacks } from '../../../../lib/callbacks'; import { getSubscriptionAutotranslateDefaultConfig } from '../../../../server/lib/getSubscriptionAutotranslateDefaultConfig'; import { roomCoordinator } from '../../../../server/lib/rooms/roomCoordinator'; @@ -54,7 +54,7 @@ export const addUserToRoom = async function ( } try { - await Apps.triggerEvent(AppEvents.IPreRoomUserJoined, room, userToBeAdded, inviter); + await Apps?.triggerEvent(AppEvents.IPreRoomUserJoined, room, userToBeAdded, inviter); } catch (error: any) { if (error.name === AppsEngineException.name) { throw new Meteor.Error('error-app-prevented', error.message); @@ -118,7 +118,7 @@ export const addUserToRoom = async function ( // Keep the current event await callbacks.run('afterJoinRoom', userToBeAdded, room); - void Apps.triggerEvent(AppEvents.IPostRoomUserJoined, room, userToBeAdded, inviter); + void Apps?.triggerEvent(AppEvents.IPostRoomUserJoined, room, userToBeAdded, inviter); }); } diff --git a/apps/meteor/app/lib/server/functions/createDirectRoom.ts b/apps/meteor/app/lib/server/functions/createDirectRoom.ts index b8383875444..cf9d2fdb704 100644 --- a/apps/meteor/app/lib/server/functions/createDirectRoom.ts +++ b/apps/meteor/app/lib/server/functions/createDirectRoom.ts @@ -1,3 +1,4 @@ +import { AppEvents, Apps } from '@rocket.chat/apps'; import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions'; import type { ISubscriptionExtraData } from '@rocket.chat/core-services'; import type { ICreatedRoom, IRoom, ISubscription, IUser } from '@rocket.chat/core-typings'; @@ -6,7 +7,6 @@ import { Random } from '@rocket.chat/random'; import { Meteor } from 'meteor/meteor'; import type { MatchKeysAndValues } from 'mongodb'; -import { Apps } from '../../../../ee/server/apps'; import { callbacks } from '../../../../lib/callbacks'; import { isTruthy } from '../../../../lib/isTruthy'; import { settings } from '../../../settings/server'; @@ -104,7 +104,7 @@ export async function createDirectRoom( _USERNAMES: usernames, }; - const prevent = await Apps.triggerEvent('IPreRoomCreatePrevent', tmpRoom).catch((error) => { + const prevent = await Apps?.triggerEvent(AppEvents.IPreRoomCreatePrevent, tmpRoom).catch((error) => { if (error.name === AppsEngineException.name) { throw new Meteor.Error('error-app-prevented', error.message); } @@ -116,7 +116,10 @@ export async function createDirectRoom( throw new Meteor.Error('error-app-prevented', 'A Rocket.Chat App prevented the room creation.'); } - const result = await Apps.triggerEvent('IPreRoomCreateModify', await Apps.triggerEvent('IPreRoomCreateExtend', tmpRoom)); + const result = await Apps?.triggerEvent( + AppEvents.IPreRoomCreateModify, + await Apps?.triggerEvent(AppEvents.IPreRoomCreateExtend, tmpRoom), + ); if (typeof result === 'object') { Object.assign(roomInfo, result); @@ -170,7 +173,7 @@ export async function createDirectRoom( await callbacks.run('afterCreateDirectRoom', insertedRoom, { members: roomMembers, creatorId: options?.creator }); - void Apps.triggerEvent('IPostRoomCreate', insertedRoom); + void Apps?.triggerEvent(AppEvents.IPostRoomCreate, insertedRoom); } return { diff --git a/apps/meteor/app/lib/server/functions/createRoom.ts b/apps/meteor/app/lib/server/functions/createRoom.ts index 3004dcd445c..517d794e68d 100644 --- a/apps/meteor/app/lib/server/functions/createRoom.ts +++ b/apps/meteor/app/lib/server/functions/createRoom.ts @@ -1,4 +1,5 @@ /* eslint-disable complexity */ +import { AppEvents, Apps } from '@rocket.chat/apps'; import { AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions'; import { Message, Team } from '@rocket.chat/core-services'; import type { ICreateRoomParams, ISubscriptionExtraData } from '@rocket.chat/core-services'; @@ -6,7 +7,6 @@ import type { ICreatedRoom, IUser, IRoom, RoomType } from '@rocket.chat/core-typ import { Rooms, Subscriptions, Users } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; -import { Apps } from '../../../../ee/server/apps/orchestrator'; import { callbacks } from '../../../../lib/callbacks'; import { beforeCreateRoomCallback } from '../../../../lib/callbacks/beforeCreateRoomCallback'; import { getSubscriptionAutotranslateDefaultConfig } from '../../../../server/lib/getSubscriptionAutotranslateDefaultConfig'; @@ -197,7 +197,7 @@ export const createRoom = async ( _USERNAMES: members, }; - const prevent = await Apps.triggerEvent('IPreRoomCreatePrevent', tmp).catch((error) => { + const prevent = await Apps?.triggerEvent(AppEvents.IPreRoomCreatePrevent, tmp).catch((error) => { if (error.name === AppsEngineException.name) { throw new Meteor.Error('error-app-prevented', error.message); } @@ -209,7 +209,10 @@ export const createRoom = async ( throw new Meteor.Error('error-app-prevented', 'A Rocket.Chat App prevented the room creation.'); } - const eventResult = await Apps.triggerEvent('IPreRoomCreateModify', await Apps.triggerEvent('IPreRoomCreateExtend', tmp)); + const eventResult = await Apps?.triggerEvent( + AppEvents.IPreRoomCreateModify, + await Apps.triggerEvent(AppEvents.IPreRoomCreateExtend, tmp), + ); if (eventResult && typeof eventResult === 'object' && delete eventResult._USERNAMES) { Object.assign(roomProps, eventResult); @@ -241,7 +244,7 @@ export const createRoom = async ( callbacks.runAsync('federation.afterCreateFederatedRoom', room, { owner, originalMemberList: members }); } - void Apps.triggerEvent('IPostRoomCreate', room); + void Apps?.triggerEvent(AppEvents.IPostRoomCreate, room); return { rid: room._id, // backwards compatible inserted: true, diff --git a/apps/meteor/app/lib/server/functions/deleteMessage.ts b/apps/meteor/app/lib/server/functions/deleteMessage.ts index cd4456b2451..26677bf37ff 100644 --- a/apps/meteor/app/lib/server/functions/deleteMessage.ts +++ b/apps/meteor/app/lib/server/functions/deleteMessage.ts @@ -1,9 +1,9 @@ +import { AppEvents, Apps } from '@rocket.chat/apps'; import { api } from '@rocket.chat/core-services'; import type { AtLeast, IMessage, IUser } from '@rocket.chat/core-typings'; import { Messages, Rooms, Uploads, Users, ReadReceipts } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; -import { Apps } from '../../../../ee/server/apps'; import { callbacks } from '../../../../lib/callbacks'; import { broadcastMessageFromData } from '../../../../server/modules/watchers/lib/messages'; import { canDeleteMessageAsync } from '../../../authorization/server/functions/canDeleteMessage'; @@ -29,14 +29,14 @@ export const deleteMessageValidatingPermission = async (message: AtLeast { - const deletedMsg = await Messages.findOneById(message._id); + const deletedMsg: IMessage | null = await Messages.findOneById(message._id); const isThread = (deletedMsg?.tcount || 0) > 0; const keepHistory = settings.get('Message_KeepHistory') || isThread; const showDeletedStatus = settings.get('Message_ShowDeletedStatus') || isThread; const bridges = Apps?.isLoaded() && Apps.getBridges(); if (deletedMsg && bridges) { - const prevent = await bridges.getListenerBridge().messageEvent('IPreMessageDeletePrevent', deletedMsg); + const prevent = await bridges.getListenerBridge().messageEvent(AppEvents.IPreMessageDeletePrevent, deletedMsg); if (prevent) { throw new Meteor.Error('error-app-prevented-deleting', 'A Rocket.Chat App prevented the message deleting.'); } @@ -95,7 +95,7 @@ export async function deleteMessage(message: IMessage, user: IUser): Promise { const originalMessage = originalMsg || (await Messages.findOneById(message._id)); + if (!originalMessage) { + throw new Error('Invalid message ID.'); + } + + let messageData: IMessage = Object.assign({}, originalMessage, message); // For the Rocket.Chat Apps :) if (message && Apps && Apps.isLoaded()) { - const appMessage = Object.assign({}, originalMessage, message); - - const prevent = await Apps.getBridges()?.getListenerBridge().messageEvent('IPreMessageUpdatedPrevent', appMessage); + const prevent = await Apps.getBridges().getListenerBridge().messageEvent(AppEvents.IPreMessageUpdatedPrevent, messageData); if (prevent) { throw new Meteor.Error('error-app-prevented-updating', 'A Rocket.Chat App prevented the message updating.'); } - let result; - result = await Apps.getBridges()?.getListenerBridge().messageEvent('IPreMessageUpdatedExtend', appMessage); - result = await Apps.getBridges()?.getListenerBridge().messageEvent('IPreMessageUpdatedModify', result); + let result = await Apps.getBridges().getListenerBridge().messageEvent(AppEvents.IPreMessageUpdatedExtend, messageData); + result = await Apps.getBridges().getListenerBridge().messageEvent(AppEvents.IPreMessageUpdatedModify, result); if (typeof result === 'object') { - message = Object.assign(appMessage, result); + Object.assign(messageData, result); } } // If we keep history of edits, insert a new message to store history information if (settings.get('Message_KeepHistory')) { - await Messages.cloneAndSaveAsHistoryById(message._id, user as Required>); + await Messages.cloneAndSaveAsHistoryById(messageData._id, user as Required>); } - Object.assign, Omit>(message, { + Object.assign(messageData, { editedAt: new Date(), editedBy: { _id: user._id, @@ -48,17 +50,16 @@ export const updateMessage = async function ( }, }); - parseUrlsInMessage(message, previewUrls); + parseUrlsInMessage(messageData, previewUrls); - const room = await Rooms.findOneById(message.rid); + const room = await Rooms.findOneById(messageData.rid); if (!room) { return; } - // TODO remove type cast - message = await Message.beforeSave({ message: message as IMessage, room, user }); + messageData = await Message.beforeSave({ message: messageData, room, user }); - const { _id, ...editedMessage } = message; + const { _id, ...editedMessage } = messageData; if (!editedMessage.msg) { delete editedMessage.md; @@ -78,7 +79,7 @@ export const updateMessage = async function ( if (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 - void Apps.getBridges()?.getListenerBridge().messageEvent('IPostMessageUpdated', message); + void Apps.getBridges()?.getListenerBridge().messageEvent(AppEvents.IPostMessageUpdated, messageData); } setImmediate(async () => { diff --git a/apps/meteor/app/lib/server/methods/deleteUserOwnAccount.ts b/apps/meteor/app/lib/server/methods/deleteUserOwnAccount.ts index ed9929622c6..f30182def68 100644 --- a/apps/meteor/app/lib/server/methods/deleteUserOwnAccount.ts +++ b/apps/meteor/app/lib/server/methods/deleteUserOwnAccount.ts @@ -1,3 +1,4 @@ +import { Apps, AppEvents } from '@rocket.chat/apps'; import { Users } from '@rocket.chat/models'; import { SHA256 } from '@rocket.chat/sha256'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; @@ -5,7 +6,6 @@ import { Accounts } from 'meteor/accounts-base'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; -import { AppEvents, Apps } from '../../../../ee/server/apps/orchestrator'; import { trim } from '../../../../lib/utils/stringUtils'; import { settings } from '../../../settings/server'; import { deleteUser } from '../functions/deleteUser'; @@ -66,7 +66,7 @@ Meteor.methods({ await deleteUser(uid, confirmRelinquish); // App IPostUserDeleted event hook - await Apps.triggerEvent(AppEvents.IPostUserDeleted, { user }); + await Apps?.triggerEvent(AppEvents.IPostUserDeleted, { user }); return true; }, diff --git a/apps/meteor/app/livechat/server/lib/Helper.ts b/apps/meteor/app/livechat/server/lib/Helper.ts index 771f50724c3..24cb2dd320c 100644 --- a/apps/meteor/app/livechat/server/lib/Helper.ts +++ b/apps/meteor/app/livechat/server/lib/Helper.ts @@ -1,3 +1,4 @@ +import { Apps, AppEvents } from '@rocket.chat/apps'; import { LivechatTransferEventType } from '@rocket.chat/apps-engine/definition/livechat'; import { api, Message, Omnichannel } from '@rocket.chat/core-services'; import type { @@ -30,7 +31,6 @@ import { import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; -import { Apps, AppEvents } from '../../../../ee/server/apps'; import { callbacks } from '../../../../lib/callbacks'; import { validateEmail as validatorFunc } from '../../../../lib/emailValidator'; import { i18n } from '../../../../server/lib/i18n'; @@ -118,7 +118,7 @@ export const createLivechatRoom = async ( const roomId = (await Rooms.insertOne(room)).insertedId; - void Apps.triggerEvent(AppEvents.IPostLivechatRoomStarted, room); + void Apps?.triggerEvent(AppEvents.IPostLivechatRoomStarted, room); await callbacks.run('livechat.newRoom', room); await sendMessage(guest, { t: 'livechat-started', msg: '', groupable: false }, room); @@ -274,7 +274,7 @@ export const removeAgentFromSubscription = async (rid: string, { _id, username } await Message.saveSystemMessage('ul', rid, username || '', { _id: user._id, username: user.username, name: user.name }); setImmediate(() => { - void Apps.triggerEvent(AppEvents.IPostLivechatAgentUnassigned, { room, user }); + void Apps?.triggerEvent(AppEvents.IPostLivechatAgentUnassigned, { room, user }); }); }; @@ -453,7 +453,7 @@ export const forwardRoomToAgent = async (room: IOmnichannelRoom, transferData: T } setImmediate(() => { - void Apps.triggerEvent(AppEvents.IPostLivechatRoomTransferred, { + void Apps?.triggerEvent(AppEvents.IPostLivechatRoomTransferred, { type: LivechatTransferEventType.AGENT, room: rid, from: oldServedBy?._id, @@ -483,7 +483,7 @@ export const updateChatDepartment = async ({ ]); setImmediate(() => { - void Apps.triggerEvent(AppEvents.IPostLivechatRoomTransferred, { + void Apps?.triggerEvent(AppEvents.IPostLivechatRoomTransferred, { type: LivechatTransferEventType.DEPARTMENT, room: rid, from: oldDepartmentId, diff --git a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts index fb1098db427..8442585d961 100644 --- a/apps/meteor/app/livechat/server/lib/LivechatTyped.ts +++ b/apps/meteor/app/livechat/server/lib/LivechatTyped.ts @@ -1,6 +1,7 @@ import dns from 'dns'; import * as util from 'util'; +import { Apps, AppEvents } from '@rocket.chat/apps'; import { Message, VideoConf, api, Omnichannel } from '@rocket.chat/core-services'; import type { IOmnichannelRoom, @@ -43,7 +44,6 @@ import moment from 'moment-timezone'; import type { Filter, FindCursor, UpdateFilter } from 'mongodb'; import UAParser from 'ua-parser-js'; -import { Apps, AppEvents } from '../../../../ee/server/apps'; import { callbacks } from '../../../../lib/callbacks'; import { trim } from '../../../../lib/utils/stringUtils'; import { i18n } from '../../../../server/lib/i18n'; @@ -330,8 +330,8 @@ class LivechatClass { * @deprecated the `AppEvents.ILivechatRoomClosedHandler` event will be removed * in the next major version of the Apps-Engine */ - void Apps.getBridges()?.getListenerBridge().livechatEvent(AppEvents.ILivechatRoomClosedHandler, newRoom); - void Apps.getBridges()?.getListenerBridge().livechatEvent(AppEvents.IPostLivechatRoomClosed, newRoom); + void Apps?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.ILivechatRoomClosedHandler, newRoom); + void Apps?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.IPostLivechatRoomClosed, newRoom); }); if (process.env.TEST_MODE) { await callbacks.run('livechat.closeRoom', { @@ -1420,7 +1420,7 @@ class LivechatClass { const ret = await LivechatVisitors.saveGuestById(_id, updateData); setImmediate(() => { - void Apps.triggerEvent(AppEvents.IPostLivechatGuestSaved, _id); + void Apps?.triggerEvent(AppEvents.IPostLivechatGuestSaved, _id); }); return ret; @@ -1786,7 +1786,7 @@ class LivechatClass { await LivechatRooms.saveRoomById(roomData); setImmediate(() => { - void Apps.triggerEvent(AppEvents.IPostLivechatRoomSaved, roomData._id); + void Apps?.triggerEvent(AppEvents.IPostLivechatRoomSaved, roomData._id); }); if (guestData?.name?.trim().length) { diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index 7b85c31f26a..96621bece8b 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -1,3 +1,4 @@ +import { Apps, AppEvents } from '@rocket.chat/apps'; import { Message, Omnichannel } from '@rocket.chat/core-services'; import type { ILivechatInquiryRecord, @@ -16,7 +17,6 @@ import { LivechatInquiry, LivechatRooms, Subscriptions, Rooms, Users } from '@ro import { Match, check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; -import { Apps, AppEvents } from '../../../../ee/server/apps'; import { callbacks } from '../../../../lib/callbacks'; import { createLivechatSubscription, @@ -172,7 +172,7 @@ export const RoutingManager: Routing = { await dispatchAgentDelegated(rid, agent.agentId); logger.debug(`Agent ${agent.agentId} assigned to inquriy ${inquiry._id}. Instances notified`); - void Apps.getBridges()?.getListenerBridge().livechatEvent(AppEvents.IPostLivechatAgentAssigned, { room, user }); + void Apps?.getBridges()?.getListenerBridge().livechatEvent(AppEvents.IPostLivechatAgentAssigned, { room, user }); return inquiry; }, diff --git a/apps/meteor/app/mailer/server/api.ts b/apps/meteor/app/mailer/server/api.ts index b50fdfd26a2..cc2caae74ba 100644 --- a/apps/meteor/app/mailer/server/api.ts +++ b/apps/meteor/app/mailer/server/api.ts @@ -1,3 +1,4 @@ +import { AppEvents, Apps } from '@rocket.chat/apps'; import type { ISetting } from '@rocket.chat/core-typings'; import { Settings } from '@rocket.chat/models'; import { escapeHTML } from '@rocket.chat/string-helpers'; @@ -7,7 +8,6 @@ import { Meteor } from 'meteor/meteor'; import stripHtml from 'string-strip-html'; import _ from 'underscore'; -import { Apps } from '../../../ee/server/apps'; import { validateEmail } from '../../../lib/emailValidator'; import { strLeft, strRightBack } from '../../../lib/utils/stringUtils'; import { i18n } from '../../../server/lib/i18n'; @@ -170,7 +170,7 @@ export const sendNoWrap = async ({ const email = { to, from, replyTo, subject, html, text, headers }; - const eventResult = await Apps.triggerEvent('IPreEmailSent', { email }); + const eventResult = await Apps?.triggerEvent(AppEvents.IPreEmailSent, { email }); setImmediate(() => Email.sendAsync(eventResult || email).catch((e) => console.error(e))); }; diff --git a/apps/meteor/app/message-pin/server/pinMessage.ts b/apps/meteor/app/message-pin/server/pinMessage.ts index 1ed0a172028..4887e360312 100644 --- a/apps/meteor/app/message-pin/server/pinMessage.ts +++ b/apps/meteor/app/message-pin/server/pinMessage.ts @@ -1,3 +1,4 @@ +import { Apps, AppEvents } from '@rocket.chat/apps'; import { Message } from '@rocket.chat/core-services'; import { isQuoteAttachment, isRegisterUser } from '@rocket.chat/core-typings'; import type { IMessage, MessageAttachment, MessageQuoteAttachment } from '@rocket.chat/core-typings'; @@ -6,7 +7,6 @@ import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; -import { Apps, AppEvents } from '../../../ee/server/apps/orchestrator'; import { isTruthy } from '../../../lib/isTruthy'; import { broadcastMessageFromData } from '../../../server/modules/watchers/lib/messages'; import { canAccessRoomAsync, roomAccessAttributes } from '../../authorization/server'; @@ -129,7 +129,7 @@ Meteor.methods({ } // App IPostMessagePinned event hook - await Apps.triggerEvent(AppEvents.IPostMessagePinned, originalMessage, await Meteor.userAsync(), originalMessage.pinned); + await Apps?.triggerEvent(AppEvents.IPostMessagePinned, originalMessage, await Meteor.userAsync(), originalMessage.pinned); const msgId = await Message.saveSystemMessage('message_pinned', originalMessage.rid, '', me, { attachments: [ @@ -216,7 +216,7 @@ Meteor.methods({ } // App IPostMessagePinned event hook - await Apps.triggerEvent(AppEvents.IPostMessagePinned, originalMessage, await Meteor.userAsync(), originalMessage.pinned); + await Apps?.triggerEvent(AppEvents.IPostMessagePinned, originalMessage, await Meteor.userAsync(), originalMessage.pinned); await Messages.setPinnedByIdAndUserId(originalMessage._id, originalMessage.pinnedBy, originalMessage.pinned); if (settings.get('Message_Read_Receipt_Store_Users')) { diff --git a/apps/meteor/app/message-star/server/starMessage.ts b/apps/meteor/app/message-star/server/starMessage.ts index 8f025d92005..9f8ba75c453 100644 --- a/apps/meteor/app/message-star/server/starMessage.ts +++ b/apps/meteor/app/message-star/server/starMessage.ts @@ -1,9 +1,9 @@ +import { Apps, AppEvents } from '@rocket.chat/apps'; import type { IMessage } from '@rocket.chat/core-typings'; import { Messages, Subscriptions, Rooms } from '@rocket.chat/models'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; -import { Apps, AppEvents } from '../../../ee/server/apps/orchestrator'; import { broadcastMessageFromData } from '../../../server/modules/watchers/lib/messages'; import { canAccessRoomAsync, roomAccessAttributes } from '../../authorization/server'; import { isTheLastMessage } from '../../lib/server/functions/isTheLastMessage'; @@ -57,7 +57,7 @@ Meteor.methods({ await Rooms.updateLastMessageStar(room._id, uid, message.starred); } - await Apps.triggerEvent(AppEvents.IPostMessageStarred, message, await Meteor.userAsync(), message.starred); + await Apps?.triggerEvent(AppEvents.IPostMessageStarred, message, await Meteor.userAsync(), message.starred); await Messages.updateUserStarById(message._id, uid, message.starred); diff --git a/apps/meteor/app/reactions/server/setReaction.ts b/apps/meteor/app/reactions/server/setReaction.ts index 27fe4d36a05..ed2271a5d4d 100644 --- a/apps/meteor/app/reactions/server/setReaction.ts +++ b/apps/meteor/app/reactions/server/setReaction.ts @@ -1,3 +1,4 @@ +import { Apps, AppEvents } from '@rocket.chat/apps'; import { api } from '@rocket.chat/core-services'; import type { IMessage, IRoom, IUser } from '@rocket.chat/core-typings'; import { Messages, EmojiCustom, Rooms, Users } from '@rocket.chat/models'; @@ -5,7 +6,6 @@ import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import _ from 'underscore'; -import { AppEvents, Apps } from '../../../ee/server/apps/orchestrator'; import { callbacks } from '../../../lib/callbacks'; import { i18n } from '../../../server/lib/i18n'; import { broadcastMessageFromData } from '../../../server/modules/watchers/lib/messages'; @@ -106,7 +106,7 @@ async function setReaction(room: IRoom, user: IUser, message: IMessage, reaction isReacted = true; } - await Apps.triggerEvent(AppEvents.IPostMessageReacted, message, user, reaction, isReacted); + await Apps?.triggerEvent(AppEvents.IPostMessageReacted, message, user, reaction, isReacted); void broadcastMessageFromData({ id: message._id, diff --git a/apps/meteor/app/statistics/server/lib/getAppsStatistics.js b/apps/meteor/app/statistics/server/lib/getAppsStatistics.js index 6337b287506..652686e6715 100644 --- a/apps/meteor/app/statistics/server/lib/getAppsStatistics.js +++ b/apps/meteor/app/statistics/server/lib/getAppsStatistics.js @@ -1,17 +1,18 @@ +import { Apps } from '@rocket.chat/apps'; import { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; -import { Apps } from '../../../../ee/server/apps'; import { Info } from '../../../utils/rocketchat.info'; export function getAppsStatistics() { return { engineVersion: Info.marketplaceApiVersion, - totalInstalled: Apps.isInitialized() && Apps.getManager().get().length, - totalActive: Apps.isInitialized() && Apps.getManager().get({ enabled: true }).length, + totalInstalled: (Apps?.isInitialized() && Apps.getManager().get().length) ?? 0, + totalActive: (Apps?.isInitialized() && Apps.getManager().get({ enabled: true }).length) ?? 0, totalFailed: - Apps.isInitialized() && - Apps.getManager() - .get({ disabled: true }) - .filter(({ app: { status } }) => status !== AppStatus.MANUALLY_DISABLED).length, + (Apps?.isInitialized() && + Apps.getManager() + .get({ disabled: true }) + .filter(({ app: { status } }) => status !== AppStatus.MANUALLY_DISABLED).length) ?? + 0, }; } diff --git a/apps/meteor/app/threads/server/methods/followMessage.ts b/apps/meteor/app/threads/server/methods/followMessage.ts index cede3dda33a..f6bae69b1aa 100644 --- a/apps/meteor/app/threads/server/methods/followMessage.ts +++ b/apps/meteor/app/threads/server/methods/followMessage.ts @@ -1,10 +1,10 @@ +import { Apps, AppEvents } from '@rocket.chat/apps'; import type { IMessage } from '@rocket.chat/core-typings'; import { Messages } from '@rocket.chat/models'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; -import { Apps, AppEvents } from '../../../../ee/server/apps/orchestrator'; import { canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom'; import { RateLimiter } from '../../../lib/server'; import { settings } from '../../../settings/server'; @@ -44,7 +44,7 @@ Meteor.methods({ const followResult = await follow({ tmid: message.tmid || message._id, uid }); const isFollowed = true; - await Apps.triggerEvent(AppEvents.IPostMessageFollowed, message, await Meteor.userAsync(), isFollowed); + await Apps?.triggerEvent(AppEvents.IPostMessageFollowed, message, await Meteor.userAsync(), isFollowed); return followResult; }, diff --git a/apps/meteor/app/threads/server/methods/unfollowMessage.ts b/apps/meteor/app/threads/server/methods/unfollowMessage.ts index c5dad123317..b50c26508eb 100644 --- a/apps/meteor/app/threads/server/methods/unfollowMessage.ts +++ b/apps/meteor/app/threads/server/methods/unfollowMessage.ts @@ -1,10 +1,10 @@ +import { Apps, AppEvents } from '@rocket.chat/apps'; import type { IMessage } from '@rocket.chat/core-typings'; import { Messages } from '@rocket.chat/models'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; -import { Apps, AppEvents } from '../../../../ee/server/apps/orchestrator'; import { canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom'; import { RateLimiter } from '../../../lib/server'; import { settings } from '../../../settings/server'; @@ -44,7 +44,7 @@ Meteor.methods({ const unfollowResult = await unfollow({ rid: message.rid, tmid: message.tmid || message._id, uid }); const isFollowed = false; - await Apps.triggerEvent(AppEvents.IPostMessageFollowed, message, await Meteor.userAsync(), isFollowed); + await Apps?.triggerEvent(AppEvents.IPostMessageFollowed, message, await Meteor.userAsync(), isFollowed); return unfollowResult; }, diff --git a/apps/meteor/ee/server/apps/index.ts b/apps/meteor/ee/server/apps/index.ts index 35f7c2cc041..0306575e00f 100644 --- a/apps/meteor/ee/server/apps/index.ts +++ b/apps/meteor/ee/server/apps/index.ts @@ -1,4 +1,4 @@ import './cron'; import './appRequestsCron'; -export { Apps, AppEvents } from './orchestrator'; +export { Apps } from './orchestrator'; diff --git a/apps/meteor/ee/server/apps/orchestrator.js b/apps/meteor/ee/server/apps/orchestrator.js index 84f9cb1372f..37c31e890e8 100644 --- a/apps/meteor/ee/server/apps/orchestrator.js +++ b/apps/meteor/ee/server/apps/orchestrator.js @@ -1,5 +1,5 @@ +import { registerOrchestrator } from '@rocket.chat/apps'; import { EssentialAppDisabledException } from '@rocket.chat/apps-engine/definition/exceptions'; -import { AppInterface } from '@rocket.chat/apps-engine/definition/metadata'; import { AppManager } from '@rocket.chat/apps-engine/server/AppManager'; import { Logger } from '@rocket.chat/logger'; import { AppLogs, Apps as AppsModel, AppsPersistence } from '@rocket.chat/models'; @@ -249,8 +249,8 @@ export class AppServerOrchestrator { } } -export const AppEvents = AppInterface; export const Apps = new AppServerOrchestrator(); +registerOrchestrator(Apps); settings.watch('Apps_Framework_Source_Package_Storage_Type', (value) => { if (!Apps.isInitialized()) { diff --git a/apps/meteor/server/lib/moderation/reportMessage.ts b/apps/meteor/server/lib/moderation/reportMessage.ts index be8b917fd6f..710ea6e1b68 100644 --- a/apps/meteor/server/lib/moderation/reportMessage.ts +++ b/apps/meteor/server/lib/moderation/reportMessage.ts @@ -1,8 +1,8 @@ +import { Apps, AppEvents } from '@rocket.chat/apps'; import type { IMessage, IUser } from '@rocket.chat/core-typings'; import { Messages, ModerationReports, Rooms, Users } from '@rocket.chat/models'; import { canAccessRoomAsync } from '../../../app/authorization/server/functions/canAccessRoom'; -import { AppEvents, Apps } from '../../../ee/server/apps'; export const reportMessage = async (messageId: IMessage['_id'], description: string, uid: IUser['_id']) => { if (!uid) { @@ -49,7 +49,7 @@ export const reportMessage = async (messageId: IMessage['_id'], description: str await ModerationReports.createWithMessageDescriptionAndUserId(message, description, roomInfo, reportedBy); - await Apps.triggerEvent(AppEvents.IPostMessageReported, message, user, description); + await Apps?.triggerEvent(AppEvents.IPostMessageReported, message, user, description); return true; }; diff --git a/apps/meteor/server/methods/deleteUser.ts b/apps/meteor/server/methods/deleteUser.ts index 4dafad7a3a0..8762cfab243 100644 --- a/apps/meteor/server/methods/deleteUser.ts +++ b/apps/meteor/server/methods/deleteUser.ts @@ -1,3 +1,4 @@ +import { Apps, AppEvents } from '@rocket.chat/apps'; import type { IUser } from '@rocket.chat/core-typings'; import { Users } from '@rocket.chat/models'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; @@ -6,7 +7,6 @@ import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission'; import { deleteUser } from '../../app/lib/server/functions/deleteUser'; -import { AppEvents, Apps } from '../../ee/server/apps/orchestrator'; declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -52,7 +52,7 @@ Meteor.methods({ await deleteUser(userId, confirmRelinquish, uid); // App IPostUserDeleted event hook - await Apps.triggerEvent(AppEvents.IPostUserDeleted, { user, performedBy: await Meteor.userAsync() }); + await Apps?.triggerEvent(AppEvents.IPostUserDeleted, { user, performedBy: await Meteor.userAsync() }); return true; }, diff --git a/apps/meteor/server/methods/eraseRoom.ts b/apps/meteor/server/methods/eraseRoom.ts index 177b3c23bb9..687b9ad6699 100644 --- a/apps/meteor/server/methods/eraseRoom.ts +++ b/apps/meteor/server/methods/eraseRoom.ts @@ -1,3 +1,4 @@ +import { AppEvents, Apps } from '@rocket.chat/apps'; import { Message, Team } from '@rocket.chat/core-services'; import { Rooms } from '@rocket.chat/models'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; @@ -7,7 +8,6 @@ import { Meteor } from 'meteor/meteor'; import { hasPermissionAsync } from '../../app/authorization/server/functions/hasPermission'; import { deleteRoom } from '../../app/lib/server/functions/deleteRoom'; import { methodDeprecationLogger } from '../../app/lib/server/lib/deprecationWarningLogger'; -import { Apps } from '../../ee/server/apps'; import { roomCoordinator } from '../lib/rooms/roomCoordinator'; export async function eraseRoom(rid: string, uid: string): Promise { @@ -36,7 +36,7 @@ export async function eraseRoom(rid: string, uid: string): Promise { } if (Apps?.isLoaded()) { - const prevent = await Apps.getBridges()?.getListenerBridge().roomEvent('IPreRoomDeletePrevent', room); + const prevent = await Apps.getBridges()?.getListenerBridge().roomEvent(AppEvents.IPreRoomDeletePrevent, room); if (prevent) { throw new Meteor.Error('error-app-prevented-deleting', 'A Rocket.Chat App prevented the room erasing.'); } @@ -54,7 +54,7 @@ export async function eraseRoom(rid: string, uid: string): Promise { } if (Apps?.isLoaded()) { - void Apps.getBridges()?.getListenerBridge().roomEvent('IPostRoomDeleted', room); + void Apps.getBridges()?.getListenerBridge().roomEvent(AppEvents.IPostRoomDeleted, room); } } diff --git a/apps/meteor/server/methods/logoutCleanUp.ts b/apps/meteor/server/methods/logoutCleanUp.ts index 9b9af5356af..502cad3c5fb 100644 --- a/apps/meteor/server/methods/logoutCleanUp.ts +++ b/apps/meteor/server/methods/logoutCleanUp.ts @@ -1,9 +1,9 @@ +import { AppEvents, Apps } from '@rocket.chat/apps'; import type { IUser } from '@rocket.chat/core-typings'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; import { check } from 'meteor/check'; import { Meteor } from 'meteor/meteor'; -import { AppEvents, Apps } from '../../ee/server/apps/orchestrator'; import { afterLogoutCleanUpCallback } from '../../lib/callbacks/afterLogoutCleanUpCallback'; declare module '@rocket.chat/ui-contexts' { @@ -22,6 +22,6 @@ Meteor.methods({ }); // App IPostUserLogout event hook - await Apps.triggerEvent(AppEvents.IPostUserLoggedOut, user); + await Apps?.triggerEvent(AppEvents.IPostUserLoggedOut, user); }, }); diff --git a/apps/meteor/server/methods/reportMessage.ts b/apps/meteor/server/methods/reportMessage.ts index 94d6fc1fd31..44087dad042 100644 --- a/apps/meteor/server/methods/reportMessage.ts +++ b/apps/meteor/server/methods/reportMessage.ts @@ -1,3 +1,4 @@ +import { Apps, AppEvents } from '@rocket.chat/apps'; import type { IMessage } from '@rocket.chat/core-typings'; import { ModerationReports, Rooms, Users, Messages } from '@rocket.chat/models'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; @@ -6,7 +7,6 @@ import { Meteor } from 'meteor/meteor'; import { canAccessRoomAsync } from '../../app/authorization/server/functions/canAccessRoom'; import { methodDeprecationLogger } from '../../app/lib/server/lib/deprecationWarningLogger'; -import { AppEvents, Apps } from '../../ee/server/apps'; declare module '@rocket.chat/ui-contexts' { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -77,7 +77,7 @@ Meteor.methods({ await ModerationReports.createWithMessageDescriptionAndUserId(message, description, roomInfo, reportedBy); - await Apps.triggerEvent(AppEvents.IPostMessageReported, message, await Meteor.userAsync(), description); + await Apps?.triggerEvent(AppEvents.IPostMessageReported, message, await Meteor.userAsync(), description); return true; }, diff --git a/apps/meteor/server/methods/saveUserProfile.ts b/apps/meteor/server/methods/saveUserProfile.ts index 5bfbba5b1b3..695742977ad 100644 --- a/apps/meteor/server/methods/saveUserProfile.ts +++ b/apps/meteor/server/methods/saveUserProfile.ts @@ -1,3 +1,4 @@ +import { Apps, AppEvents } from '@rocket.chat/apps'; import type { UserStatus } from '@rocket.chat/core-typings'; import { Users } from '@rocket.chat/models'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; @@ -12,7 +13,6 @@ import { saveUserIdentity } from '../../app/lib/server/functions/saveUserIdentit import { passwordPolicy } from '../../app/lib/server/lib/passwordPolicy'; import { settings as rcSettings } from '../../app/settings/server'; import { setUserStatusMethod } from '../../app/user-status/server/methods/setUserStatus'; -import { AppEvents, Apps } from '../../ee/server/apps/orchestrator'; import { compareUserPassword } from '../lib/compareUserPassword'; import { compareUserPasswordHistory } from '../lib/compareUserPasswordHistory'; @@ -156,7 +156,7 @@ async function saveUserProfile( // App IPostUserUpdated event hook const updatedUser = await Users.findOneById(this.userId); - await Apps.triggerEvent(AppEvents.IPostUserUpdated, { user: updatedUser, previousUser: user }); + await Apps?.triggerEvent(AppEvents.IPostUserUpdated, { user: updatedUser, previousUser: user }); return true; } diff --git a/apps/meteor/server/services/apps-engine/service.ts b/apps/meteor/server/services/apps-engine/service.ts index e72ce3cbce0..7e36a937e6a 100644 --- a/apps/meteor/server/services/apps-engine/service.ts +++ b/apps/meteor/server/services/apps-engine/service.ts @@ -1,3 +1,4 @@ +import { Apps, AppEvents } from '@rocket.chat/apps'; import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; import { AppStatusUtils } from '@rocket.chat/apps-engine/definition/AppStatus'; import type { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata'; @@ -6,7 +7,6 @@ import type { IAppStorageItem } from '@rocket.chat/apps-engine/server/storage'; import type { IAppsEngineService } from '@rocket.chat/core-services'; import { ServiceClassInternal } from '@rocket.chat/core-services'; -import { Apps, AppEvents } from '../../../ee/server/apps/orchestrator'; import { SystemLogger } from '../../lib/logger/system'; export class AppsEngineService extends ServiceClassInternal implements IAppsEngineService { @@ -16,7 +16,7 @@ export class AppsEngineService extends ServiceClassInternal implements IAppsEngi super(); this.onEvent('presence.status', async ({ user, previousStatus }): Promise => { - await Apps.triggerEvent(AppEvents.IPostUserStatusChanged, { + await Apps?.triggerEvent(AppEvents.IPostUserStatusChanged, { user, currentStatus: user.status, previousStatus, @@ -24,68 +24,70 @@ export class AppsEngineService extends ServiceClassInternal implements IAppsEngi }); this.onEvent('apps.added', async (appId: string): Promise => { - Apps.getRocketChatLogger().debug(`"apps.added" event received for app "${appId}"`); + Apps?.getRocketChatLogger().debug(`"apps.added" event received for app "${appId}"`); // if the app already exists in this instance, don't load it again - const app = Apps.getManager()?.getOneById(appId); + const app = Apps?.getManager()?.getOneById(appId); if (app) { - Apps.getRocketChatLogger().info(`"apps.added" event received for app "${appId}", but it already exists in this instance`); + Apps?.getRocketChatLogger().info(`"apps.added" event received for app "${appId}", but it already exists in this instance`); return; } - await Apps.getManager()?.addLocal(appId); + await Apps?.getManager()?.addLocal(appId); }); this.onEvent('apps.removed', async (appId: string): Promise => { - Apps.getRocketChatLogger().debug(`"apps.removed" event received for app "${appId}"`); - const app = Apps.getManager()?.getOneById(appId); + Apps?.getRocketChatLogger().debug(`"apps.removed" event received for app "${appId}"`); + const app = Apps?.getManager()?.getOneById(appId); if (!app) { - Apps.getRocketChatLogger().info(`"apps.removed" event received for app "${appId}", but it couldn't be found in this instance`); + Apps?.getRocketChatLogger().info(`"apps.removed" event received for app "${appId}", but it couldn't be found in this instance`); return; } - await Apps.getManager()?.removeLocal(appId); + await Apps?.getManager()?.removeLocal(appId); }); this.onEvent('apps.updated', async (appId: string): Promise => { - Apps.getRocketChatLogger().debug(`"apps.updated" event received for app "${appId}"`); - const storageItem = await Apps.getStorage()?.retrieveOne(appId); + Apps?.getRocketChatLogger().debug(`"apps.updated" event received for app "${appId}"`); + const storageItem = await Apps?.getStorage()?.retrieveOne(appId); if (!storageItem) { - Apps.getRocketChatLogger().info(`"apps.updated" event received for app "${appId}", but it couldn't be found in the storage`); + Apps?.getRocketChatLogger().info(`"apps.updated" event received for app "${appId}", but it couldn't be found in the storage`); return; } - const appPackage = await Apps.getAppSourceStorage()?.fetch(storageItem); + const appPackage = await Apps?.getAppSourceStorage()?.fetch(storageItem); if (!appPackage) { return; } - await Apps.getManager()?.updateLocal(storageItem, appPackage); + await Apps?.getManager()?.updateLocal(storageItem, appPackage); }); this.onEvent('apps.statusUpdate', async (appId: string, status: AppStatus): Promise => { - Apps.getRocketChatLogger().debug(`"apps.statusUpdate" event received for app "${appId}" with status "${status}"`); - const app = Apps.getManager()?.getOneById(appId); + Apps?.getRocketChatLogger().debug(`"apps.statusUpdate" event received for app "${appId}" with status "${status}"`); + const app = Apps?.getManager()?.getOneById(appId); if (!app) { - Apps.getRocketChatLogger().info(`"apps.statusUpdate" event received for app "${appId}", but it couldn't be found in this instance`); + Apps?.getRocketChatLogger().info( + `"apps.statusUpdate" event received for app "${appId}", but it couldn't be found in this instance`, + ); return; } if (app.getStatus() === status) { - Apps.getRocketChatLogger().info(`"apps.statusUpdate" event received for app "${appId}", but the status is the same`); + Apps?.getRocketChatLogger().info(`"apps.statusUpdate" event received for app "${appId}", but the status is the same`); return; } if (AppStatusUtils.isEnabled(status)) { - await Apps.getManager()?.enable(appId).catch(SystemLogger.error); + await Apps?.getManager()?.enable(appId).catch(SystemLogger.error); } else if (AppStatusUtils.isDisabled(status)) { - await Apps.getManager()?.disable(appId, status, true).catch(SystemLogger.error); + await Apps?.getManager()?.disable(appId, status, true).catch(SystemLogger.error); } }); this.onEvent('apps.settingUpdated', async (appId: string, setting): Promise => { - Apps.getRocketChatLogger().debug(`"apps.settingUpdated" event received for app "${appId}"`, { setting }); - const app = Apps.getManager()?.getOneById(appId); + Apps?.getRocketChatLogger().debug(`"apps.settingUpdated" event received for app "${appId}"`, { setting }); + const app = Apps?.getManager()?.getOneById(appId); const oldSetting = app?.getStorageItem().settings[setting.id].value; // avoid updating the setting if the value is the same, @@ -94,30 +96,30 @@ export class AppsEngineService extends ServiceClassInternal implements IAppsEngi // so we need to convert it to JSON stringified to compare it if (JSON.stringify(oldSetting) === JSON.stringify(setting.value)) { - Apps.getRocketChatLogger().info( + Apps?.getRocketChatLogger().info( `"apps.settingUpdated" event received for setting ${setting.id} of app "${appId}", but the setting value is the same`, ); return; } - await Apps.getManager() + await Apps?.getManager() ?.getSettingsManager() .updateAppSetting(appId, setting as any); }); } isInitialized(): boolean { - return Apps.isInitialized(); + return Boolean(Apps?.isInitialized()); } async getApps(query: IGetAppsFilter): Promise { - return Apps.getManager() + return Apps?.getManager() ?.get(query) .map((app) => app.getApp().getInfo()); } async getAppStorageItemById(appId: string): Promise { - const app = Apps.getManager()?.getOneById(appId); + const app = Apps?.getManager()?.getOneById(appId); if (!app) { return; diff --git a/apps/meteor/server/services/video-conference/service.ts b/apps/meteor/server/services/video-conference/service.ts index 90a7a330242..87fe279d0d9 100644 --- a/apps/meteor/server/services/video-conference/service.ts +++ b/apps/meteor/server/services/video-conference/service.ts @@ -1,3 +1,4 @@ +import { Apps } from '@rocket.chat/apps'; import type { AppVideoConfProviderManager } from '@rocket.chat/apps-engine/server/managers'; import type { IVideoConfService, VideoConferenceJoinOptions } from '@rocket.chat/core-services'; import { api, ServiceClassInternal } from '@rocket.chat/core-services'; @@ -41,7 +42,6 @@ import { settings } from '../../../app/settings/server'; import { updateCounter } from '../../../app/statistics/server/functions/updateStatsCounter'; import { getUserAvatarURL } from '../../../app/utils/server/getUserAvatarURL'; import { getUserPreference } from '../../../app/utils/server/lib/getUserPreference'; -import { Apps } from '../../../ee/server/apps'; import { callbacks } from '../../../lib/callbacks'; import { availabilityErrors } from '../../../lib/videoConference/constants'; import { readSecondaryPreferred } from '../../database/readSecondaryPreferred'; @@ -832,7 +832,7 @@ export class VideoConfService extends ServiceClassInternal implements IVideoConf throw new Error('apps-engine-not-loaded'); } - const manager = Apps.getManager()?.getVideoConfProviderManager(); + const manager = Apps?.getManager()?.getVideoConfProviderManager(); if (!manager) { throw new Error(availabilityErrors.NO_APP); } diff --git a/apps/meteor/server/startup/migrations/v291.ts b/apps/meteor/server/startup/migrations/v291.ts index 8923f3b282c..f4fdbb74344 100644 --- a/apps/meteor/server/startup/migrations/v291.ts +++ b/apps/meteor/server/startup/migrations/v291.ts @@ -1,8 +1,7 @@ +import { Apps, type AppMetadataStorage } from '@rocket.chat/apps'; import type { IAppStorageItem } from '@rocket.chat/apps-engine/server/storage'; import { Settings } from '@rocket.chat/models'; -import { Apps } from '../../../ee/server/apps'; -import type { AppRealStorage } from '../../../ee/server/apps/storage'; import { addMigration } from '../../lib/migrations'; addMigration({ @@ -13,13 +12,17 @@ addMigration({ await Settings.removeById('Apps_Framework_Development_Mode'); await Settings.removeById('Apps_Framework_enabled'); + if (!Apps) { + throw new Error('Apps Orchestrator not registered.'); + } + Apps.initialize(); - const appsStorage = Apps.getStorage() as AppRealStorage; + const appsStorage = Apps.getStorage(); const apps = await appsStorage.retrieveAll(); - const promises: Array> = []; + const promises: Array> = []; apps.forEach((app) => promises.push( diff --git a/apps/meteor/server/startup/migrations/v292.ts b/apps/meteor/server/startup/migrations/v292.ts index 7f590f4038e..beec6967a90 100644 --- a/apps/meteor/server/startup/migrations/v292.ts +++ b/apps/meteor/server/startup/migrations/v292.ts @@ -1,7 +1,7 @@ +import { Apps } from '@rocket.chat/apps'; import type { AppSignatureManager } from '@rocket.chat/apps-engine/server/managers/AppSignatureManager'; import type { IAppStorageItem } from '@rocket.chat/apps-engine/server/storage'; -import { Apps } from '../../../ee/server/apps'; import type { AppRealStorage } from '../../../ee/server/apps/storage'; import { addMigration } from '../../lib/migrations'; @@ -9,6 +9,10 @@ addMigration({ version: 292, name: 'Add checksum signature to existing apps', async up() { + if (!Apps) { + throw new Error('Apps Orchestrator not registered.'); + } + Apps.initialize(); const sigMan = Apps.getManager()?.getSignatureManager() as AppSignatureManager; diff --git a/apps/meteor/server/startup/migrations/v294.ts b/apps/meteor/server/startup/migrations/v294.ts index abcb20d079e..832043740f8 100644 --- a/apps/meteor/server/startup/migrations/v294.ts +++ b/apps/meteor/server/startup/migrations/v294.ts @@ -1,13 +1,17 @@ +import { Apps } from '@rocket.chat/apps'; import type { AppSignatureManager } from '@rocket.chat/apps-engine/server/managers/AppSignatureManager'; import type { IAppStorageItem } from '@rocket.chat/apps-engine/server/storage'; -import { Apps } from '../../../ee/server/apps'; import type { AppRealStorage } from '../../../ee/server/apps/storage'; import { addMigration } from '../../lib/migrations'; addMigration({ version: 294, async up() { + if (!Apps) { + throw new Error('Apps Orchestrator not registered.'); + } + Apps.initialize(); const sigMan = Apps.getManager()?.getSignatureManager() as AppSignatureManager; diff --git a/packages/apps/src/AppsEngine.ts b/packages/apps/src/AppsEngine.ts index 117e93c0ec2..856bc125379 100644 --- a/packages/apps/src/AppsEngine.ts +++ b/packages/apps/src/AppsEngine.ts @@ -8,6 +8,7 @@ export type { IVisitorPhone as IAppsVisitorPhone, } from '@rocket.chat/apps-engine/definition/livechat'; export type { IMessage as IAppsMessage } from '@rocket.chat/apps-engine/definition/messages'; +export { AppInterface as AppEvents } from '@rocket.chat/apps-engine/definition/metadata'; export type { IUser as IAppsUser } from '@rocket.chat/apps-engine/definition/users'; export type { IRole as IAppsRole } from '@rocket.chat/apps-engine/definition/roles'; export type { IRoom as IAppsRoom } from '@rocket.chat/apps-engine/definition/rooms'; @@ -18,3 +19,5 @@ export type { VideoConference as AppsVideoConference, } from '@rocket.chat/apps-engine/definition/videoConferences'; export { AppManager } from '@rocket.chat/apps-engine/server/AppManager'; +export { AppBridges } from '@rocket.chat/apps-engine/server/bridges'; +export { AppMetadataStorage } from '@rocket.chat/apps-engine/server/storage'; diff --git a/packages/apps/src/IAppServerOrchestrator.ts b/packages/apps/src/IAppServerOrchestrator.ts index dbfc5aee7a2..2f1f7db5d4b 100644 --- a/packages/apps/src/IAppServerOrchestrator.ts +++ b/packages/apps/src/IAppServerOrchestrator.ts @@ -1,12 +1,16 @@ import type { AppManager } from '@rocket.chat/apps-engine/server/AppManager'; +import type { AppSourceStorage } from '@rocket.chat/apps-engine/server/storage'; import type { Logger } from '@rocket.chat/logger'; import type { IAppsPersistenceModel } from '@rocket.chat/model-typings'; +import type { AppBridges, AppEvents, AppMetadataStorage } from './AppsEngine'; import type { IAppServerNotifier } from './IAppServerNotifier'; import type { IAppConvertersMap } from './converters'; export interface IAppServerOrchestrator { initialize(): void; + isInitialized(): boolean; + isLoaded(): boolean; getNotifier(): IAppServerNotifier; isDebugging(): boolean; debugLog(...args: any[]): void; @@ -14,4 +18,8 @@ export interface IAppServerOrchestrator { getConverters(): IAppConvertersMap; getPersistenceModel(): IAppsPersistenceModel; getRocketChatLogger(): Logger; + triggerEvent(event: AppEvents, ...payload: any[]): Promise; + getBridges(): AppBridges; + getStorage(): AppMetadataStorage; + getAppSourceStorage(): AppSourceStorage; } diff --git a/packages/apps/src/bridges/IListenerBridge.ts b/packages/apps/src/bridges/IListenerBridge.ts new file mode 100644 index 00000000000..faf34118cd3 --- /dev/null +++ b/packages/apps/src/bridges/IListenerBridge.ts @@ -0,0 +1,48 @@ +import type { IMessage, IRoom, IUser, ILivechatDepartment, ILivechatVisitor, IOmnichannelRoom } from '@rocket.chat/core-typings'; + +import type { AppEvents } from '../AppsEngine'; + +declare module '@rocket.chat/apps-engine/server/bridges' { + interface IListenerBridge { + messageEvent(int: 'IPostMessageDeleted', message: IMessage, userDeleted: IUser): Promise; + messageEvent(int: 'IPostMessageReacted', message: IMessage, userReacted: IUser, reaction: string, isReacted: boolean): Promise; + messageEvent(int: 'IPostMessageFollowed', message: IMessage, userFollowed: IUser, isFollowed: boolean): Promise; + messageEvent(int: 'IPostMessagePinned', message: IMessage, userPinned: IUser, isPinned: boolean): Promise; + messageEvent(int: 'IPostMessageStarred', message: IMessage, userStarred: IUser, isStarred: boolean): Promise; + messageEvent(int: 'IPostMessageReported', message: IMessage, userReported: IUser, reason: boolean): Promise; + + messageEvent( + int: 'IPreMessageSentPrevent' | 'IPreMessageDeletePrevent' | 'IPreMessageUpdatedPrevent', + message: IMessage, + ): Promise; + messageEvent( + int: 'IPreMessageSentExtend' | 'IPreMessageSentModify' | 'IPreMessageUpdatedExtend' | 'IPreMessageUpdatedModify', + message: IMessage, + ): Promise; + messageEvent(int: 'IPostMessageSent' | 'IPostMessageUpdated', message: IMessage): Promise; + + roomEvent(int: 'IPreRoomUserJoined' | 'IPostRoomUserJoined', room: IRoom, joiningUser: IUser, invitingUser?: IUser): Promise; + roomEvent(int: 'IPreRoomUserLeave' | 'IPostRoomUserLeave', room: IRoom, leavingUser: IUser): Promise; + + roomEvent(int: 'IPreRoomCreatePrevent' | 'IPreRoomDeletePrevent', room: IRoom): Promise; + roomEvent(int: 'IPreRoomCreateExtend' | 'IPreRoomCreateModify', room: IRoom): Promise; + roomEvent(int: 'IPostRoomCreate' | 'IPostRoomDeleted', room: IRoom): Promise; + + livechatEvent( + int: 'IPostLivechatAgentAssigned' | 'IPostLivechatAgentUnassigned', + data: { user: IUser; room: IOmnichannelRoom }, + ): Promise; + livechatEvent( + int: 'IPostLivechatRoomTransferred', + data: { type: 'agent'; room: IRoom['_id']; from: IUser['_id']; to: IUser['_id'] }, + ): Promise; + livechatEvent( + int: 'IPostLivechatRoomTransferred', + data: { type: 'department'; room: IRoom['_id']; from: ILivechatDepartment['_id']; to: ILivechatDepartment['_id'] }, + ): Promise; + livechatEvent(int: 'IPostLivechatGuestSaved', data: ILivechatVisitor['_id']): Promise; + livechatEvent(int: 'IPostLivechatRoomSaved', data: IRoom['_id']): Promise; + livechatEvent(int: 'ILivechatRoomClosedHandler' | 'IPostLivechatRoomStarted' | 'IPostLivechatRoomClosed', data: IRoom): Promise; + livechatEvent(int: AppEvents | AppEvents[keyof AppEvents], data: any): Promise; + } +} diff --git a/packages/apps/src/index.ts b/packages/apps/src/index.ts index e137fa3cf00..837749af62c 100644 --- a/packages/apps/src/index.ts +++ b/packages/apps/src/index.ts @@ -1,4 +1,7 @@ +import './bridges/IListenerBridge'; + export * from './converters'; export * from './AppsEngine'; export * from './IAppServerNotifier'; export * from './IAppServerOrchestrator'; +export * from './orchestrator'; diff --git a/packages/apps/src/orchestrator.ts b/packages/apps/src/orchestrator.ts new file mode 100644 index 00000000000..4e3a53d9d5f --- /dev/null +++ b/packages/apps/src/orchestrator.ts @@ -0,0 +1,7 @@ +import type { IAppServerOrchestrator } from './IAppServerOrchestrator'; + +export let Apps: IAppServerOrchestrator | undefined; + +export function registerOrchestrator(orch: IAppServerOrchestrator): void { + Apps = orch; +} diff --git a/yarn.lock b/yarn.lock index 72c94c3be6d..41869c1761c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -39995,7 +39995,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:~5.3.3": +"typescript@npm:~5.3.2, typescript@npm:~5.3.3": version: 5.3.3 resolution: "typescript@npm:5.3.3" bin: @@ -40005,7 +40005,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@~5.3.3#~builtin": +"typescript@patch:typescript@~5.3.2#~builtin, typescript@patch:typescript@~5.3.3#~builtin": version: 5.3.3 resolution: "typescript@patch:typescript@npm%3A5.3.3#~builtin::version=5.3.3&hash=85af82" bin: