From a75f9742dd6794470d8fbfac4e38c69bf37db08e Mon Sep 17 00:00:00 2001 From: Thassio Victor Date: Thu, 2 Mar 2023 14:46:05 -0300 Subject: [PATCH] [FIX] Notify apps engine event through ms api (#28169) Co-authored-by: Guilherme Gazzo <5263975+ggazzo@users.noreply.github.com> --- .../client/apps/communication/websockets.js | 20 +++- .../ee/server/apps/communication/events.ts | 12 +++ .../server/apps/communication/websockets.ts | 45 +++------ .../modules/listeners/listeners.module.ts | 43 ++++++++- .../server/services/apps-engine/service.ts | 91 ++++++++++++++++++- packages/core-services/src/Events.ts | 11 +++ 6 files changed, 185 insertions(+), 37 deletions(-) create mode 100644 apps/meteor/ee/server/apps/communication/events.ts diff --git a/apps/meteor/ee/client/apps/communication/websockets.js b/apps/meteor/ee/client/apps/communication/websockets.js index f944454e5675..c7415e1ce82a 100644 --- a/apps/meteor/ee/client/apps/communication/websockets.js +++ b/apps/meteor/ee/client/apps/communication/websockets.js @@ -50,9 +50,23 @@ export class AppWebsocketReceiver extends Emitter { } onCommandAddedOrUpdated = (command) => { - APIClient.get('/v1/commands.get', { command }).then((result) => { - slashCommands.add(result.command); - }); + const retryOnFailure = (retries) => { + APIClient.get('/v1/commands.get', { command }) + .then((result) => { + slashCommands.add(result.command); + }) + .catch((error) => { + if (retries - 1 === 0) { + throw error; + } + + setTimeout(() => { + retryOnFailure(retries - 1); + }, 3000); + }); + }; + + retryOnFailure(3); }; onCommandRemovedOrDisabled = (command) => { diff --git a/apps/meteor/ee/server/apps/communication/events.ts b/apps/meteor/ee/server/apps/communication/events.ts new file mode 100644 index 000000000000..1096bd7e3f7c --- /dev/null +++ b/apps/meteor/ee/server/apps/communication/events.ts @@ -0,0 +1,12 @@ +export enum AppEvents { + APP_ADDED = 'app/added', + APP_REMOVED = 'app/removed', + APP_UPDATED = 'app/updated', + APP_STATUS_CHANGE = 'app/statusUpdate', + APP_SETTING_UPDATED = 'app/settingUpdated', + COMMAND_ADDED = 'command/added', + COMMAND_DISABLED = 'command/disabled', + COMMAND_UPDATED = 'command/updated', + COMMAND_REMOVED = 'command/removed', + ACTIONS_CHANGED = 'actions/changed', +} diff --git a/apps/meteor/ee/server/apps/communication/websockets.ts b/apps/meteor/ee/server/apps/communication/websockets.ts index 99a7f17258ae..94646477e32e 100644 --- a/apps/meteor/ee/server/apps/communication/websockets.ts +++ b/apps/meteor/ee/server/apps/communication/websockets.ts @@ -3,24 +3,14 @@ import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; import { AppStatusUtils } from '@rocket.chat/apps-engine/definition/AppStatus'; import type { ISetting } from '@rocket.chat/core-typings'; import type { IStreamer } from 'meteor/rocketchat:streamer'; +import { api } from '@rocket.chat/core-services'; import { SystemLogger } from '../../../../server/lib/logger/system'; import notifications from '../../../../app/notifications/server/lib/Notifications'; import type { AppServerOrchestrator } from '../orchestrator'; +import { AppEvents } from './events'; -export enum AppEvents { - APP_ADDED = 'app/added', - APP_REMOVED = 'app/removed', - APP_UPDATED = 'app/updated', - APP_STATUS_CHANGE = 'app/statusUpdate', - APP_SETTING_UPDATED = 'app/settingUpdated', - COMMAND_ADDED = 'command/added', - COMMAND_DISABLED = 'command/disabled', - COMMAND_UPDATED = 'command/updated', - COMMAND_REMOVED = 'command/removed', - ACTIONS_CHANGED = 'actions/changed', -} - +export { AppEvents }; export class AppServerListener { private orch: AppServerOrchestrator; @@ -153,13 +143,11 @@ export class AppServerNotifier { } async appAdded(appId: string): Promise { - this.engineStreamer.emit(AppEvents.APP_ADDED, appId); - this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_ADDED, appId); + api.broadcast('apps.added', appId); } async appRemoved(appId: string): Promise { - this.engineStreamer.emit(AppEvents.APP_REMOVED, appId); - this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_REMOVED, appId); + api.broadcast('apps.removed', appId); } async appUpdated(appId: string): Promise { @@ -168,8 +156,7 @@ export class AppServerNotifier { return; } - this.engineStreamer.emit(AppEvents.APP_UPDATED, appId); - this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_UPDATED, appId); + api.broadcast('apps.updated', appId); } async appStatusUpdated(appId: string, status: AppStatus): Promise { @@ -181,8 +168,7 @@ export class AppServerNotifier { } } - this.engineStreamer.emit(AppEvents.APP_STATUS_CHANGE, { appId, status }); - this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_STATUS_CHANGE, { appId, status }); + api.broadcast('apps.statusUpdate', appId, status); } async appSettingsChange(appId: string, setting: ISetting): Promise { @@ -191,31 +177,26 @@ export class AppServerNotifier { return; } - this.engineStreamer.emit(AppEvents.APP_SETTING_UPDATED, { appId, setting }); - this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_SETTING_UPDATED, { appId }); + api.broadcast('apps.settingUpdated', appId, setting); } async commandAdded(command: string): Promise { - this.engineStreamer.emit(AppEvents.COMMAND_ADDED, command); - this.clientStreamer.emitWithoutBroadcast(AppEvents.COMMAND_ADDED, command); + api.broadcast('command.added', command); } async commandDisabled(command: string): Promise { - this.engineStreamer.emit(AppEvents.COMMAND_DISABLED, command); - this.clientStreamer.emitWithoutBroadcast(AppEvents.COMMAND_DISABLED, command); + api.broadcast('command.disabled', command); } async commandUpdated(command: string): Promise { - this.engineStreamer.emit(AppEvents.COMMAND_UPDATED, command); - this.clientStreamer.emitWithoutBroadcast(AppEvents.COMMAND_UPDATED, command); + api.broadcast('command.updated', command); } async commandRemoved(command: string): Promise { - this.engineStreamer.emit(AppEvents.COMMAND_REMOVED, command); - this.clientStreamer.emitWithoutBroadcast(AppEvents.COMMAND_REMOVED, command); + api.broadcast('command.removed', command); } async actionsChanged(): Promise { - this.clientStreamer.emitWithoutBroadcast(AppEvents.ACTIONS_CHANGED); + api.broadcast('actions.changed'); } } diff --git a/apps/meteor/server/modules/listeners/listeners.module.ts b/apps/meteor/server/modules/listeners/listeners.module.ts index 321a3cd59f2b..c08bb5cdc81c 100644 --- a/apps/meteor/server/modules/listeners/listeners.module.ts +++ b/apps/meteor/server/modules/listeners/listeners.module.ts @@ -1,5 +1,6 @@ import { UserStatus, isSettingColor } from '@rocket.chat/core-typings'; -import type { IUser, IRoom, VideoConference } from '@rocket.chat/core-typings'; +import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; +import type { IUser, IRoom, VideoConference, ISetting } from '@rocket.chat/core-typings'; import { parse } from '@rocket.chat/message-parser'; import type { IServiceClass } from '@rocket.chat/core-services'; import { EnterpriseSettings } from '@rocket.chat/core-services'; @@ -380,5 +381,45 @@ export class ListenersModule { service.onEvent('watch.priorities', async ({ clientAction, diff, id }): Promise => { notifications.notifyLoggedInThisInstance('omnichannel.priority-changed', { id, clientAction, name: diff?.name }); }); + + service.onEvent('apps.added', (appId: string) => { + notifications.streamApps.emitWithoutBroadcast('app/added', appId); + }); + + service.onEvent('apps.removed', (appId: string) => { + notifications.streamApps.emitWithoutBroadcast('app/removed', appId); + }); + + service.onEvent('apps.updated', (appId: string) => { + notifications.streamApps.emitWithoutBroadcast('app/updated', appId); + }); + + service.onEvent('apps.statusUpdate', (appId: string, status: AppStatus) => { + notifications.streamApps.emitWithoutBroadcast('app/statusUpdate', { appId, status }); + }); + + service.onEvent('apps.settingUpdated', (appId: string, setting: ISetting) => { + notifications.streamApps.emitWithoutBroadcast('app/settingUpdated', { appId, setting }); + }); + + service.onEvent('command.added', (command: string) => { + notifications.streamApps.emitWithoutBroadcast('command/added', command); + }); + + service.onEvent('command.disabled', (command: string) => { + notifications.streamApps.emitWithoutBroadcast('command/disabled', command); + }); + + service.onEvent('command.updated', (command: string) => { + notifications.streamApps.emitWithoutBroadcast('command/updated', command); + }); + + service.onEvent('command.removed', (command: string) => { + notifications.streamApps.emitWithoutBroadcast('command/removed', command); + }); + + service.onEvent('actions.changed', () => { + notifications.streamApps.emitWithoutBroadcast('actions/changed'); + }); } } diff --git a/apps/meteor/server/services/apps-engine/service.ts b/apps/meteor/server/services/apps-engine/service.ts index d493b00c29d0..984ff5076f63 100644 --- a/apps/meteor/server/services/apps-engine/service.ts +++ b/apps/meteor/server/services/apps-engine/service.ts @@ -1,12 +1,20 @@ import { ServiceClassInternal } from '@rocket.chat/core-services'; import type { IAppsEngineService } from '@rocket.chat/core-services'; +import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; +import { AppStatusUtils } from '@rocket.chat/apps-engine/definition/AppStatus'; +import type { ISetting } from '@rocket.chat/core-typings'; import { Apps, AppEvents } from '../../../ee/server/apps/orchestrator'; +import { AppEvents as AppLifeCycleEvents } from '../../../ee/server/apps/communication/websockets'; +import notifications from '../../../app/notifications/server/lib/Notifications'; +import { SystemLogger } from '../../lib/logger/system'; export class AppsEngineService extends ServiceClassInternal implements IAppsEngineService { protected name = 'apps-engine'; - async created() { + constructor() { + super(); + this.onEvent('presence.status', async ({ user, previousStatus }): Promise => { Apps.triggerEvent(AppEvents.IPostUserStatusChanged, { user, @@ -14,5 +22,86 @@ export class AppsEngineService extends ServiceClassInternal implements IAppsEngi previousStatus, }); }); + + this.onEvent('apps.added', async (appId: string): Promise => { + await (Apps.getManager() as any)?.loadOne(appId); + notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.APP_ADDED, appId); + }); + + this.onEvent('apps.removed', async (appId: string): Promise => { + const app = Apps.getManager()?.getOneById(appId); + + if (!app) { + return; + } + + await Apps.getManager()?.removeLocal(appId); + notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.APP_REMOVED, appId); + }); + + this.onEvent('apps.updated', async (appId: string): Promise => { + const storageItem = await Apps.getStorage()?.retrieveOne(appId); + + if (!storageItem) { + return; + } + + const appPackage = await Apps.getAppSourceStorage()?.fetch(storageItem); + + if (!appPackage) { + return; + } + + await Apps.getManager()?.updateLocal(storageItem, appPackage); + + notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.APP_UPDATED, appId); + }); + + this.onEvent('apps.statusUpdate', async (appId: string, status: AppStatus): Promise => { + const app = Apps.getManager()?.getOneById(appId); + + if (!app || app.getStatus() === status) { + return; + } + + if (AppStatusUtils.isEnabled(status)) { + await Apps.getManager()?.enable(appId).catch(SystemLogger.error); + notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.APP_STATUS_CHANGE, { appId, status }); + } else if (AppStatusUtils.isDisabled(status)) { + await Apps.getManager()?.disable(appId, status, true).catch(SystemLogger.error); + notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.APP_STATUS_CHANGE, { appId, status }); + } + }); + + this.onEvent('apps.settingUpdated', async (appId: string, setting: ISetting): Promise => { + const appManager = Apps.getManager(); + + if (!appManager) { + return; + } + + await appManager.getSettingsManager().updateAppSetting(appId, setting as any); + notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.APP_SETTING_UPDATED, { appId }); + }); + + this.onEvent('command.added', (command: string) => { + notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.COMMAND_ADDED, command); + }); + + this.onEvent('command.disabled', (command: string) => { + notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.COMMAND_DISABLED, command); + }); + + this.onEvent('command.updated', (command: string) => { + notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.COMMAND_UPDATED, command); + }); + + this.onEvent('command.removed', (command: string) => { + notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.COMMAND_REMOVED, command); + }); + + this.onEvent('actions.changed', () => { + notifications.streamApps.emitWithoutBroadcast(AppLifeCycleEvents.ACTIONS_CHANGED); + }); } } diff --git a/packages/core-services/src/Events.ts b/packages/core-services/src/Events.ts index 9f55cdc3f3e7..8b073aa302ca 100644 --- a/packages/core-services/src/Events.ts +++ b/packages/core-services/src/Events.ts @@ -1,3 +1,4 @@ +import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; import type { IUIKitInteraction } from '@rocket.chat/apps-engine/definition/uikit'; import type { IEmailInbox, @@ -158,4 +159,14 @@ export type EventSignatures = { id: string; diff?: Record; }): void; + 'apps.added'(appId: string): void; + 'apps.removed'(appId: string): void; + 'apps.updated'(appId: string): void; + 'apps.statusUpdate'(appId: string, status: AppStatus): void; + 'apps.settingUpdated'(appId: string, setting: ISetting): void; + 'command.added'(command: string): void; + 'command.disabled'(command: string): void; + 'command.updated'(command: string): void; + 'command.removed'(command: string): void; + 'actions.changed'(): void; };