Files
upstream-rocketchat/apps/meteor/ee/server/apps/communication/websockets.ts
upstream-mirror 30810eecd7
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

215 lines
7.2 KiB
TypeScript

import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus';
import { AppStatusUtils } from '@rocket.chat/apps-engine/definition/AppStatus';
import type { ISetting as AppsSetting } from '@rocket.chat/apps-engine/definition/settings';
import { api } from '@rocket.chat/core-services';
import { InstanceStatus } from '@rocket.chat/instance-status';
import { AppEvents } from './events';
import { SystemLogger } from '../../../../server/lib/logger/system';
import notifications from '../../../../server/lib/notifications/core/lib/Notifications';
import type { IStreamer } from '../../../../server/modules/streamer/types';
import type { AppServerOrchestrator } from '../orchestrator';
export { AppEvents };
export class AppServerListener {
private orch: AppServerOrchestrator;
engineStreamer: IStreamer<'apps-engine'>;
clientStreamer: IStreamer<'apps'>;
received;
constructor(
orch: AppServerOrchestrator,
engineStreamer: IStreamer<'apps-engine'>,
clientStreamer: IStreamer<'apps'>,
received: Map<any, any>,
) {
this.orch = orch;
this.engineStreamer = engineStreamer;
this.clientStreamer = clientStreamer;
this.received = received;
this.engineStreamer.on(AppEvents.APP_STATUS_CHANGE, this.onAppStatusUpdated.bind(this));
this.engineStreamer.on(AppEvents.APP_REMOVED, this.onAppRemoved.bind(this));
this.engineStreamer.on(AppEvents.APP_UPDATED, this.onAppUpdated.bind(this));
this.engineStreamer.on(AppEvents.APP_ADDED, this.onAppAdded.bind(this));
this.engineStreamer.on(AppEvents.ACTIONS_CHANGED, this.onActionsChanged.bind(this));
this.engineStreamer.on(AppEvents.APP_SETTING_UPDATED, this.onAppSettingUpdated.bind(this));
this.engineStreamer.on(AppEvents.COMMAND_ADDED, this.onCommandAdded.bind(this));
this.engineStreamer.on(AppEvents.COMMAND_DISABLED, this.onCommandDisabled.bind(this));
this.engineStreamer.on(AppEvents.COMMAND_UPDATED, this.onCommandUpdated.bind(this));
this.engineStreamer.on(AppEvents.COMMAND_REMOVED, this.onCommandRemoved.bind(this));
}
async onAppAdded(appId: string): Promise<void> {
await (this.orch.getManager()! as any).addLocal(appId); // TO-DO: fix type
this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_ADDED, appId);
}
async onAppStatusUpdated({ appId, status }: { appId: string; status: AppStatus }): Promise<void> {
const app = this.orch.getManager()?.getOneById(appId);
if (!app || (await app.getStatus()) === status) {
return;
}
this.received.set(`${AppEvents.APP_STATUS_CHANGE}_${appId}`, {
appId,
status,
when: new Date(),
});
if (AppStatusUtils.isEnabled(status)) {
await this.orch.getManager()?.enable(appId).catch(SystemLogger.error);
this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_STATUS_CHANGE, { appId, status });
} else if (AppStatusUtils.isDisabled(status)) {
await this.orch.getManager()?.disable(appId, status, true).catch(SystemLogger.error);
this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_STATUS_CHANGE, { appId, status });
}
}
async onAppSettingUpdated({ appId, setting }: { appId: string; setting: AppsSetting }): Promise<void> {
this.received.set(`${AppEvents.APP_SETTING_UPDATED}_${appId}_${setting.id}`, {
appId,
setting,
when: new Date(),
});
await this.orch
.getManager()!
.getSettingsManager()
.updateAppSetting(appId, setting as any); // TO-DO: fix type of `setting`
this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_SETTING_UPDATED, { appId, setting });
}
async onAppUpdated(appId: string): Promise<void> {
this.received.set(`${AppEvents.APP_UPDATED}_${appId}`, { appId, when: new Date() });
const storageItem = await this.orch.getStorage()!.retrieveOne(appId);
const appPackage = await this.orch.getAppSourceStorage()!.fetch(storageItem);
const isEnabled = AppStatusUtils.isEnabled(storageItem.status);
if (isEnabled) {
await this.orch.getManager()!.updateAndStartupLocal(storageItem, appPackage);
} else {
await this.orch.getManager()!.updateAndInitializeLocal(storageItem, appPackage);
}
this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_UPDATED, appId);
}
async onAppRemoved(appId: string): Promise<void> {
const app = this.orch.getManager()!.getOneById(appId);
if (!app) {
return;
}
await this.orch.getManager()!.removeLocal(appId);
this.clientStreamer.emitWithoutBroadcast(AppEvents.APP_REMOVED, appId);
}
async onCommandAdded(command: string): Promise<void> {
this.clientStreamer.emitWithoutBroadcast(AppEvents.COMMAND_ADDED, command);
}
async onCommandDisabled(command: string): Promise<void> {
this.clientStreamer.emitWithoutBroadcast(AppEvents.COMMAND_DISABLED, command);
}
async onCommandUpdated(command: string): Promise<void> {
this.clientStreamer.emitWithoutBroadcast(AppEvents.COMMAND_UPDATED, command);
}
async onCommandRemoved(command: string): Promise<void> {
this.clientStreamer.emitWithoutBroadcast(AppEvents.COMMAND_REMOVED, command);
}
async onActionsChanged(): Promise<void> {
this.clientStreamer.emitWithoutBroadcast(AppEvents.ACTIONS_CHANGED);
}
}
export class AppServerNotifier {
engineStreamer: IStreamer<'apps-engine'>;
clientStreamer: IStreamer<'apps'>;
received: Map<any, any>;
listener: AppServerListener;
constructor(orch: AppServerOrchestrator) {
this.engineStreamer = notifications.streamAppsEngine;
// This is used to broadcast to the web clients
this.clientStreamer = notifications.streamApps;
this.received = new Map();
this.listener = new AppServerListener(orch, this.engineStreamer, this.clientStreamer, this.received);
}
async appAdded(appId: string): Promise<void> {
void api.broadcast('apps.added', appId);
}
async appRemoved(appId: string): Promise<void> {
void api.broadcast('apps.removed', appId);
}
async appUpdated(appId: string): Promise<void> {
if (this.received.has(`${AppEvents.APP_UPDATED}_${appId}`)) {
this.received.delete(`${AppEvents.APP_UPDATED}_${appId}`);
return;
}
void api.broadcast('apps.updated', appId, InstanceStatus.id());
}
async appStatusUpdated(appId: string, status: AppStatus): Promise<void> {
if (this.received.has(`${AppEvents.APP_STATUS_CHANGE}_${appId}`)) {
const details = this.received.get(`${AppEvents.APP_STATUS_CHANGE}_${appId}`);
if (details.status === status) {
this.received.delete(`${AppEvents.APP_STATUS_CHANGE}_${appId}`);
return;
}
}
void api.broadcast('apps.statusUpdate', appId, status);
}
async appSettingsChange(appId: string, setting: AppsSetting): Promise<void> {
if (this.received.has(`${AppEvents.APP_SETTING_UPDATED}_${appId}_${setting.id}`)) {
this.received.delete(`${AppEvents.APP_SETTING_UPDATED}_${appId}_${setting.id}`);
return;
}
void api.broadcast('apps.settingUpdated', appId, setting);
}
async commandAdded(command: string): Promise<void> {
void api.broadcast('command.added', command);
}
async commandDisabled(command: string): Promise<void> {
void api.broadcast('command.disabled', command);
}
async commandUpdated(command: string): Promise<void> {
void api.broadcast('command.updated', command);
}
async commandRemoved(command: string): Promise<void> {
void api.broadcast('command.removed', command);
}
async actionsChanged(): Promise<void> {
void api.broadcast('actions.changed');
}
}