mirror of
https://github.com/RocketChat/Rocket.Chat.git
synced 2025-12-28 14:58:55 +00:00
feat(outbound): Apps engine bridge (#36390)
Co-authored-by: Lucas Pelegrino <16467257+lucas-a-pelegrino@users.noreply.github.com> Co-authored-by: Diego Sampaio <8591547+sampaiodiego@users.noreply.github.com>
This commit is contained in:
parent
0b4f3d3c27
commit
fccb53718c
@ -16,6 +16,7 @@ import { AppLivechatBridge } from './livechat';
|
||||
import { AppMessageBridge } from './messages';
|
||||
import { AppModerationBridge } from './moderation';
|
||||
import { AppOAuthAppsBridge } from './oauthApps';
|
||||
import { OutboundCommunicationBridge } from './outboundCommunication';
|
||||
import { AppPersistenceBridge } from './persistence';
|
||||
import { AppRoleBridge } from './roles';
|
||||
import { AppRoomBridge } from './rooms';
|
||||
@ -57,6 +58,7 @@ export class RealAppBridges extends AppBridges {
|
||||
this._roleBridge = new AppRoleBridge(orch);
|
||||
this._emailBridge = new AppEmailBridge(orch);
|
||||
this._contactBridge = new AppContactBridge(orch);
|
||||
this._outboundMessageBridge = new OutboundCommunicationBridge(orch);
|
||||
}
|
||||
|
||||
getCommandBridge() {
|
||||
@ -139,6 +141,10 @@ export class RealAppBridges extends AppBridges {
|
||||
return this._videoConfBridge;
|
||||
}
|
||||
|
||||
getOutboundMessageBridge() {
|
||||
return this._outboundMessageBridge;
|
||||
}
|
||||
|
||||
getOAuthAppsBridge() {
|
||||
return this._oAuthBridge;
|
||||
}
|
||||
|
||||
45
apps/meteor/app/apps/server/bridges/outboundCommunication.ts
Normal file
45
apps/meteor/app/apps/server/bridges/outboundCommunication.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import type { IAppServerOrchestrator } from '@rocket.chat/apps';
|
||||
import type {
|
||||
IOutboundEmailMessageProvider,
|
||||
IOutboundMessageProviders,
|
||||
IOutboundPhoneMessageProvider,
|
||||
} from '@rocket.chat/apps-engine/definition/outboundComunication';
|
||||
import { OutboundMessageBridge } from '@rocket.chat/apps-engine/server/bridges';
|
||||
|
||||
import { getOutboundService } from '../../../livechat/server/lib/outboundcommunication';
|
||||
|
||||
export class OutboundCommunicationBridge extends OutboundMessageBridge {
|
||||
constructor(private readonly orch: IAppServerOrchestrator) {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async registerPhoneProvider(provider: IOutboundPhoneMessageProvider, appId: string): Promise<void> {
|
||||
try {
|
||||
this.orch.debugLog(`App ${appId} is registering a phone outbound provider.`);
|
||||
getOutboundService().outboundMessageProvider.registerPhoneProvider(provider);
|
||||
} catch (err) {
|
||||
this.orch.getRocketChatLogger().error({ appId, err, msg: 'Failed to register phone provider' });
|
||||
throw new Error('error-registering-provider');
|
||||
}
|
||||
}
|
||||
|
||||
protected async registerEmailProvider(provider: IOutboundEmailMessageProvider, appId: string): Promise<void> {
|
||||
try {
|
||||
this.orch.debugLog(`App ${appId} is registering an email outbound provider.`);
|
||||
getOutboundService().outboundMessageProvider.registerEmailProvider(provider);
|
||||
} catch (err) {
|
||||
this.orch.getRocketChatLogger().error({ appId, err, msg: 'Failed to register email provider' });
|
||||
throw new Error('error-registering-provider');
|
||||
}
|
||||
}
|
||||
|
||||
protected async unRegisterProvider(provider: IOutboundMessageProviders, appId: string): Promise<void> {
|
||||
try {
|
||||
this.orch.debugLog(`App ${appId} is unregistering an outbound provider.`);
|
||||
getOutboundService().outboundMessageProvider.unregisterProvider(appId, provider.type);
|
||||
} catch (err) {
|
||||
this.orch.getRocketChatLogger().error({ appId, err, msg: 'Failed to unregister provider' });
|
||||
throw new Error('error-unregistering-provider');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
import type { IOutboundMessageProviderService } from '@rocket.chat/core-typings';
|
||||
import { makeFunction } from '@rocket.chat/patch-injection';
|
||||
|
||||
export const getOutboundService = makeFunction((): IOutboundMessageProviderService => {
|
||||
throw new Error('error-no-license');
|
||||
});
|
||||
@ -1,15 +1,26 @@
|
||||
import type { IOutboundProvider, ValidOutboundProvider } from '@rocket.chat/core-typings';
|
||||
import { Apps } from '@rocket.chat/apps';
|
||||
import type {
|
||||
IOutboundProvider,
|
||||
ValidOutboundProvider,
|
||||
IOutboundMessageProviderService,
|
||||
IOutboundProviderMetadata,
|
||||
} from '@rocket.chat/core-typings';
|
||||
import { ValidOutboundProviderList } from '@rocket.chat/core-typings';
|
||||
|
||||
import { getOutboundService } from '../../../../../../app/livechat/server/lib/outboundcommunication';
|
||||
import { OutboundMessageProvider } from '../../../../../../server/lib/OutboundMessageProvider';
|
||||
|
||||
export class OutboundMessageProviderService {
|
||||
export class OutboundMessageProviderService implements IOutboundMessageProviderService {
|
||||
private readonly provider: OutboundMessageProvider;
|
||||
|
||||
constructor() {
|
||||
this.provider = new OutboundMessageProvider();
|
||||
}
|
||||
|
||||
get outboundMessageProvider() {
|
||||
return this.provider;
|
||||
}
|
||||
|
||||
private isProviderValid(type: any): type is ValidOutboundProvider {
|
||||
return ValidOutboundProviderList.includes(type);
|
||||
}
|
||||
@ -21,6 +32,41 @@ export class OutboundMessageProviderService {
|
||||
|
||||
return this.provider.getOutboundMessageProviders(type);
|
||||
}
|
||||
|
||||
public getProviderMetadata(providerId: string): Promise<IOutboundProviderMetadata> {
|
||||
const provider = this.provider.findOneByProviderId(providerId);
|
||||
if (!provider) {
|
||||
throw new Error('error-invalid-provider');
|
||||
}
|
||||
|
||||
return this.getProviderManager().getProviderMetadata(provider.appId, provider.type);
|
||||
}
|
||||
|
||||
private getProviderManager() {
|
||||
if (!Apps.self?.isLoaded()) {
|
||||
throw new Error('apps-engine-not-loaded');
|
||||
}
|
||||
|
||||
const manager = Apps.self?.getManager()?.getOutboundCommunicationProviderManager();
|
||||
if (!manager) {
|
||||
throw new Error('apps-engine-not-configured-correctly');
|
||||
}
|
||||
|
||||
return manager;
|
||||
}
|
||||
|
||||
public sendMessage(providerId: string, body: any) {
|
||||
const provider = this.provider.findOneByProviderId(providerId);
|
||||
if (!provider) {
|
||||
throw new Error('error-invalid-provider');
|
||||
}
|
||||
|
||||
return this.getProviderManager().sendOutboundMessage(provider.appId, provider.type, body);
|
||||
}
|
||||
}
|
||||
|
||||
export const outboundMessageProvider = new OutboundMessageProviderService();
|
||||
|
||||
getOutboundService.patch(() => {
|
||||
return outboundMessageProvider;
|
||||
});
|
||||
|
||||
@ -30,6 +30,9 @@ const outboundCommsEndpoints = API.v1.get(
|
||||
providerType: {
|
||||
type: 'string',
|
||||
},
|
||||
documentationUrl: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@ -3,14 +3,7 @@ import type {
|
||||
IOutboundMessageProviders,
|
||||
IOutboundPhoneMessageProvider,
|
||||
} from '@rocket.chat/apps-engine/definition/outboundComunication';
|
||||
import type { ValidOutboundProvider, IOutboundProvider } from '@rocket.chat/core-typings';
|
||||
|
||||
interface IOutboundMessageProvider {
|
||||
registerPhoneProvider(provider: IOutboundPhoneMessageProvider): void;
|
||||
registerEmailProvider(provider: IOutboundEmailMessageProvider): void;
|
||||
getOutboundMessageProviders(type?: ValidOutboundProvider): IOutboundProvider[];
|
||||
unregisterProvider(appId: string, providerType: string): void;
|
||||
}
|
||||
import type { ValidOutboundProvider, IOutboundProvider, IOutboundMessageProvider } from '@rocket.chat/core-typings';
|
||||
|
||||
export class OutboundMessageProvider implements IOutboundMessageProvider {
|
||||
private readonly outboundMessageProviders: Map<ValidOutboundProvider, IOutboundMessageProviders[]>;
|
||||
@ -22,6 +15,17 @@ export class OutboundMessageProvider implements IOutboundMessageProvider {
|
||||
]);
|
||||
}
|
||||
|
||||
public findOneByProviderId(providerId: string) {
|
||||
for (const providers of this.outboundMessageProviders.values()) {
|
||||
for (const provider of providers) {
|
||||
if (provider.appId === providerId) {
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public registerPhoneProvider(provider: IOutboundPhoneMessageProvider): void {
|
||||
this.outboundMessageProviders.set('phone', [...(this.outboundMessageProviders.get('phone') || []), provider]);
|
||||
}
|
||||
@ -36,6 +40,7 @@ export class OutboundMessageProvider implements IOutboundMessageProvider {
|
||||
providerId: provider.appId,
|
||||
providerName: provider.name,
|
||||
providerType: provider.type,
|
||||
...(provider.documentationUrl && { documentationUrl: provider.documentationUrl }),
|
||||
...(provider.supportsTemplates && { supportsTemplates: provider.supportsTemplates }),
|
||||
}));
|
||||
}
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
import { JsonRpcError, Defined } from 'jsonrpc-lite';
|
||||
import type { IOutboundMessageProviders } from '@rocket.chat/apps-engine/definition/outboundCommunication/IOutboundCommsProvider.ts';
|
||||
|
||||
import { AppObjectRegistry } from '../AppObjectRegistry.ts';
|
||||
import { AppAccessorsInstance } from '../lib/accessors/mod.ts';
|
||||
import { Logger } from '../lib/logger.ts';
|
||||
|
||||
export default async function outboundMessageHandler(call: string, params: unknown): Promise<JsonRpcError | Defined> {
|
||||
const [, providerName, methodName] = call.split(':');
|
||||
const provider = AppObjectRegistry.get<IOutboundMessageProviders>(`outboundCommunication:${providerName}`);
|
||||
if (!provider) {
|
||||
return new JsonRpcError('error-invalid-provider', -32000);
|
||||
}
|
||||
const method = provider[methodName as keyof IOutboundMessageProviders];
|
||||
const logger = AppObjectRegistry.get<Logger>('logger');
|
||||
const args = (params as Array<unknown>) ?? [];
|
||||
|
||||
try {
|
||||
logger?.debug(`Executing ${methodName} on outbound communication provider...`);
|
||||
|
||||
// deno-lint-ignore ban-types
|
||||
return await (method as Function).apply(provider, [
|
||||
...args,
|
||||
AppAccessorsInstance.getReader(),
|
||||
AppAccessorsInstance.getModifier(),
|
||||
AppAccessorsInstance.getHttp(),
|
||||
AppAccessorsInstance.getPersistence(),
|
||||
]);
|
||||
} catch (e) {
|
||||
return new JsonRpcError(e.message, -32000);
|
||||
}
|
||||
}
|
||||
@ -13,6 +13,10 @@ import type { ISlashCommand } from '@rocket.chat/apps-engine/definition/slashcom
|
||||
import type { IProcessor } from '@rocket.chat/apps-engine/definition/scheduler/IProcessor.ts';
|
||||
import type { IApi } from '@rocket.chat/apps-engine/definition/api/IApi.ts';
|
||||
import type { IVideoConfProvider } from '@rocket.chat/apps-engine/definition/videoConfProviders/IVideoConfProvider.ts';
|
||||
import type {
|
||||
IOutboundPhoneMessageProvider,
|
||||
IOutboundEmailMessageProvider,
|
||||
} from '@rocket.chat/apps-engine/definition/outboundCommunication/IOutboundCommsProvider.ts';
|
||||
|
||||
import { Http } from './http.ts';
|
||||
import { HttpExtend } from './extenders/HttpExtender.ts';
|
||||
@ -188,6 +192,17 @@ export class AppAccessors {
|
||||
return this._proxy.provideVideoConfProvider(provider);
|
||||
},
|
||||
},
|
||||
outboundCommunication: {
|
||||
_proxy: this.proxify('getConfigurationExtend:outboundCommunication'),
|
||||
registerEmailProvider(provider: IOutboundEmailMessageProvider) {
|
||||
AppObjectRegistry.set(`outboundCommunication:${provider.name}-${provider.type}`, provider);
|
||||
return this._proxy.registerEmailProvider(provider);
|
||||
},
|
||||
registerPhoneProvider(provider: IOutboundPhoneMessageProvider) {
|
||||
AppObjectRegistry.set(`outboundCommunication:${provider.name}-${provider.type}`, provider);
|
||||
return this._proxy.registerPhoneProvider(provider);
|
||||
},
|
||||
},
|
||||
slashCommands: {
|
||||
_proxy: this.proxify('getConfigurationExtend:slashCommands'),
|
||||
provideSlashCommand(slashcommand: ISlashCommand) {
|
||||
|
||||
@ -23,12 +23,14 @@ import handleApp from './handlers/app/handler.ts';
|
||||
import handleScheduler from './handlers/scheduler-handler.ts';
|
||||
import registerErrorListeners from './error-handlers.ts';
|
||||
import { sendMetrics } from './lib/metricsCollector.ts';
|
||||
import outboundMessageHandler from './handlers/outboundcomms-handler.ts';
|
||||
|
||||
type Handlers = {
|
||||
app: typeof handleApp;
|
||||
api: typeof apiHandler;
|
||||
slashcommand: typeof slashcommandHandler;
|
||||
videoconference: typeof videoConferenceHandler;
|
||||
outboundCommunication: typeof outboundMessageHandler;
|
||||
scheduler: typeof handleScheduler;
|
||||
ping: (method: string, params: unknown) => 'pong';
|
||||
};
|
||||
@ -41,6 +43,7 @@ async function requestRouter({ type, payload }: Messenger.JsonRpcRequest): Promi
|
||||
api: apiHandler,
|
||||
slashcommand: slashcommandHandler,
|
||||
videoconference: videoConferenceHandler,
|
||||
outboundCommunication: outboundMessageHandler,
|
||||
scheduler: handleScheduler,
|
||||
ping: (_method, _params) => 'pong',
|
||||
};
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { IApiExtend } from './IApiExtend';
|
||||
import type { IExternalComponentsExtend } from './IExternalComponentsExtend';
|
||||
import type { IHttpExtend } from './IHttp';
|
||||
import type { IOutboundCommunicationProviderExtend } from './IOutboundCommunicationProviderExtend';
|
||||
import type { ISchedulerExtend } from './ISchedulerExtend';
|
||||
import type { ISettingsExtend } from './ISettingsExtend';
|
||||
import type { ISlashCommandsExtend } from './ISlashCommandsExtend';
|
||||
@ -33,4 +34,7 @@ export interface IConfigurationExtend {
|
||||
|
||||
/** Accessor for declaring the videoconf providers which your App provides. */
|
||||
readonly videoConfProviders: IVideoConfProvidersExtend;
|
||||
|
||||
/** Accessor for declaring outbound communication providers */
|
||||
readonly outboundCommunication: IOutboundCommunicationProviderExtend;
|
||||
}
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
import type { IOutboundEmailMessageProvider, IOutboundPhoneMessageProvider } from '../outboundComunication';
|
||||
|
||||
export interface IOutboundCommunicationProviderExtend {
|
||||
registerPhoneProvider(provider: IOutboundPhoneMessageProvider): Promise<void>;
|
||||
registerEmailProvider(provider: IOutboundEmailMessageProvider): Promise<void>;
|
||||
}
|
||||
@ -56,3 +56,4 @@ export * from './IVideoConferenceExtend';
|
||||
export * from './IVideoConferenceRead';
|
||||
export * from './IVideoConfProvidersExtend';
|
||||
export * from './IModerationModify';
|
||||
export * from './IOutboundCommunicationProviderExtend';
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import type { IOutboundMessage } from './IOutboundMessage';
|
||||
import type { IOutboundProviderTemplate } from './IOutboundProviderTemplate';
|
||||
|
||||
type ProviderMetadata = {
|
||||
appId: string;
|
||||
appName: string;
|
||||
export type ProviderMetadata = {
|
||||
providerId: string;
|
||||
providerName: string;
|
||||
providerType: 'phone' | 'email';
|
||||
supportsTemplates: boolean; // Indicates if the provider uses templates or not
|
||||
templates: Record<string, IOutboundProviderTemplate[]>; // Format: { '+1121221212': [{ template }] }
|
||||
@ -30,3 +30,7 @@ export interface IOutboundEmailMessageProvider extends IOutboundMessageProviderB
|
||||
}
|
||||
|
||||
export type IOutboundMessageProviders = IOutboundPhoneMessageProvider | IOutboundEmailMessageProvider;
|
||||
|
||||
export const ValidOutboundProviderList = ['phone', 'email'] as const;
|
||||
|
||||
export type ValidOutboundProvider = (typeof ValidOutboundProviderList)[number];
|
||||
|
||||
@ -26,6 +26,7 @@ import {
|
||||
AppSlashCommandManager,
|
||||
AppVideoConfProviderManager,
|
||||
} from './managers';
|
||||
import { AppOutboundCommunicationProviderManager } from './managers/AppOutboundCommunicationProviderManager';
|
||||
import { AppRuntimeManager } from './managers/AppRuntimeManager';
|
||||
import { AppSignatureManager } from './managers/AppSignatureManager';
|
||||
import { UIActionButtonManager } from './managers/UIActionButtonManager';
|
||||
@ -97,6 +98,8 @@ export class AppManager {
|
||||
|
||||
private readonly videoConfProviderManager: AppVideoConfProviderManager;
|
||||
|
||||
private readonly outboundCommunicationProviderManager: AppOutboundCommunicationProviderManager;
|
||||
|
||||
private readonly signatureManager: AppSignatureManager;
|
||||
|
||||
private readonly runtime: AppRuntimeManager;
|
||||
@ -147,6 +150,7 @@ export class AppManager {
|
||||
this.schedulerManager = new AppSchedulerManager(this);
|
||||
this.uiActionButtonManager = new UIActionButtonManager(this);
|
||||
this.videoConfProviderManager = new AppVideoConfProviderManager(this);
|
||||
this.outboundCommunicationProviderManager = new AppOutboundCommunicationProviderManager(this);
|
||||
this.signatureManager = new AppSignatureManager(this);
|
||||
this.runtime = new AppRuntimeManager(this);
|
||||
|
||||
@ -198,6 +202,10 @@ export class AppManager {
|
||||
return this.videoConfProviderManager;
|
||||
}
|
||||
|
||||
public getOutboundCommunicationProviderManager(): AppOutboundCommunicationProviderManager {
|
||||
return this.outboundCommunicationProviderManager;
|
||||
}
|
||||
|
||||
public getLicenseManager(): AppLicenseManager {
|
||||
return this.licenseManager;
|
||||
}
|
||||
@ -1075,6 +1083,7 @@ export class AppManager {
|
||||
this.accessorManager.purifyApp(app.getID());
|
||||
this.uiActionButtonManager.clearAppActionButtons(app.getID());
|
||||
this.videoConfProviderManager.unregisterProviders(app.getID());
|
||||
await this.outboundCommunicationProviderManager.unregisterProviders(app.getID());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1148,6 +1157,7 @@ export class AppManager {
|
||||
this.listenerManager.registerListeners(app);
|
||||
this.listenerManager.releaseEssentialEvents(app);
|
||||
this.videoConfProviderManager.registerProviders(app.getID());
|
||||
await this.outboundCommunicationProviderManager.registerProviders(app.getID());
|
||||
} else {
|
||||
await this.purgeAppConfig(app);
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@ import type {
|
||||
ISlashCommandsExtend,
|
||||
IUIExtend,
|
||||
IVideoConfProvidersExtend,
|
||||
IOutboundCommunicationProviderExtend,
|
||||
} from '../../definition/accessors';
|
||||
|
||||
export class ConfigurationExtend implements IConfigurationExtend {
|
||||
@ -20,5 +21,6 @@ export class ConfigurationExtend implements IConfigurationExtend {
|
||||
public readonly scheduler: ISchedulerExtend,
|
||||
public readonly ui: IUIExtend,
|
||||
public readonly videoConfProviders: IVideoConfProvidersExtend,
|
||||
public readonly outboundCommunication: IOutboundCommunicationProviderExtend,
|
||||
) {}
|
||||
}
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import type { IOutboundCommunicationProviderExtend } from '../../definition/accessors/IOutboundCommunicationProviderExtend';
|
||||
import type { IOutboundPhoneMessageProvider, IOutboundEmailMessageProvider } from '../../definition/outboundComunication';
|
||||
import type { AppOutboundCommunicationProviderManager } from '../managers/AppOutboundCommunicationProviderManager';
|
||||
|
||||
export class OutboundMessageProviderExtend implements IOutboundCommunicationProviderExtend {
|
||||
constructor(
|
||||
private readonly manager: AppOutboundCommunicationProviderManager,
|
||||
private readonly appId: string,
|
||||
) {}
|
||||
|
||||
public registerPhoneProvider(provider: IOutboundPhoneMessageProvider): Promise<void> {
|
||||
return Promise.resolve(this.manager.addProvider(this.appId, provider));
|
||||
}
|
||||
|
||||
public registerEmailProvider(provider: IOutboundEmailMessageProvider): Promise<void> {
|
||||
return Promise.resolve(this.manager.addProvider(this.appId, provider));
|
||||
}
|
||||
}
|
||||
@ -20,6 +20,7 @@ import { ModifyUpdater } from './ModifyUpdater';
|
||||
import { Notifier } from './Notifier';
|
||||
import { OAuthAppsModify } from './OAuthAppsModify';
|
||||
import { OAuthAppsReader } from './OAuthAppsReader';
|
||||
import { OutboundMessageProviderExtend } from './OutboundCommunicationProviderExtend';
|
||||
import { Persistence } from './Persistence';
|
||||
import { PersistenceRead } from './PersistenceRead';
|
||||
import { Reader } from './Reader';
|
||||
@ -92,4 +93,5 @@ export {
|
||||
VideoConfProviderExtend,
|
||||
OAuthAppsModify,
|
||||
OAuthAppsReader,
|
||||
OutboundMessageProviderExtend,
|
||||
};
|
||||
|
||||
@ -14,6 +14,7 @@ import type { LivechatBridge } from './LivechatBridge';
|
||||
import type { MessageBridge } from './MessageBridge';
|
||||
import type { ModerationBridge } from './ModerationBridge';
|
||||
import type { OAuthAppsBridge } from './OAuthAppsBridge';
|
||||
import type { OutboundMessageBridge } from './OutboundMessagesBridge';
|
||||
import type { PersistenceBridge } from './PersistenceBridge';
|
||||
import type { RoleBridge } from './RoleBridge';
|
||||
import type { RoomBridge } from './RoomBridge';
|
||||
@ -48,7 +49,8 @@ export type Bridge =
|
||||
| VideoConferenceBridge
|
||||
| OAuthAppsBridge
|
||||
| ModerationBridge
|
||||
| RoleBridge;
|
||||
| RoleBridge
|
||||
| OutboundMessageBridge;
|
||||
|
||||
export abstract class AppBridges {
|
||||
public abstract getCommandBridge(): CommandBridge;
|
||||
@ -102,4 +104,6 @@ export abstract class AppBridges {
|
||||
public abstract getThreadBridge(): ThreadBridge;
|
||||
|
||||
public abstract getRoleBridge(): RoleBridge;
|
||||
|
||||
public abstract getOutboundMessageBridge(): OutboundMessageBridge;
|
||||
}
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
import { BaseBridge } from './BaseBridge';
|
||||
import type {
|
||||
IOutboundEmailMessageProvider,
|
||||
IOutboundMessageProviders,
|
||||
IOutboundPhoneMessageProvider,
|
||||
} from '../../definition/outboundComunication';
|
||||
import { PermissionDeniedError } from '../errors/PermissionDeniedError';
|
||||
import { AppPermissionManager } from '../managers/AppPermissionManager';
|
||||
import { AppPermissions } from '../permissions/AppPermissions';
|
||||
|
||||
export abstract class OutboundMessageBridge extends BaseBridge {
|
||||
public async doRegisterPhoneProvider(info: IOutboundPhoneMessageProvider, appId: string): Promise<void> {
|
||||
if (this.hasProviderPermission(appId)) {
|
||||
return this.registerPhoneProvider(info, appId);
|
||||
}
|
||||
}
|
||||
|
||||
public async doRegisterEmailProvider(info: IOutboundEmailMessageProvider, appId: string): Promise<void> {
|
||||
if (this.hasProviderPermission(appId)) {
|
||||
return this.registerEmailProvider(info, appId);
|
||||
}
|
||||
}
|
||||
|
||||
public async doUnRegisterProvider(info: IOutboundMessageProviders, appId: string): Promise<void> {
|
||||
if (this.hasProviderPermission(appId)) {
|
||||
return this.unRegisterProvider(info, appId);
|
||||
}
|
||||
}
|
||||
|
||||
private hasProviderPermission(appId: string): boolean {
|
||||
if (AppPermissionManager.hasPermission(appId, AppPermissions.outboundComms.provide)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
AppPermissionManager.notifyAboutError(
|
||||
new PermissionDeniedError({
|
||||
appId,
|
||||
missingPermissions: [AppPermissions.outboundComms.provide],
|
||||
}),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract registerPhoneProvider(info: IOutboundPhoneMessageProvider, appId: string): Promise<void>;
|
||||
|
||||
protected abstract registerEmailProvider(info: IOutboundEmailMessageProvider, appId: string): Promise<void>;
|
||||
|
||||
protected abstract unRegisterProvider(info: IOutboundMessageProviders, appId: string): Promise<void>;
|
||||
}
|
||||
@ -14,6 +14,7 @@ import { IListenerBridge } from './IListenerBridge';
|
||||
import { LivechatBridge } from './LivechatBridge';
|
||||
import { MessageBridge } from './MessageBridge';
|
||||
import { ModerationBridge } from './ModerationBridge';
|
||||
import { OutboundMessageBridge } from './OutboundMessagesBridge';
|
||||
import { PersistenceBridge } from './PersistenceBridge';
|
||||
import { RoleBridge } from './RoleBridge';
|
||||
import { RoomBridge } from './RoomBridge';
|
||||
@ -51,4 +52,5 @@ export {
|
||||
IInternalFederationBridge,
|
||||
ModerationBridge,
|
||||
RoleBridge,
|
||||
OutboundMessageBridge,
|
||||
};
|
||||
|
||||
@ -25,6 +25,7 @@ import {
|
||||
Modify,
|
||||
Notifier,
|
||||
OAuthAppsReader,
|
||||
OutboundMessageProviderExtend,
|
||||
Persistence,
|
||||
PersistenceRead,
|
||||
Reader,
|
||||
@ -114,8 +115,9 @@ export class AppAccessorManager {
|
||||
const excs = new ExternalComponentsExtend(this.manager.getExternalComponentManager(), appId);
|
||||
const scheduler = new SchedulerExtend(this.manager.getSchedulerManager(), appId);
|
||||
const ui = new UIExtend(this.manager.getUIActionButtonManager(), appId);
|
||||
const outboundComms = new OutboundMessageProviderExtend(this.manager.getOutboundCommunicationProviderManager(), appId);
|
||||
|
||||
this.configExtenders.set(appId, new ConfigurationExtend(htt, sets, cmds, apis, excs, scheduler, ui, videoConf));
|
||||
this.configExtenders.set(appId, new ConfigurationExtend(htt, sets, cmds, apis, excs, scheduler, ui, videoConf, outboundComms));
|
||||
}
|
||||
|
||||
return this.configExtenders.get(appId);
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
import type { AppAccessorManager } from '.';
|
||||
import { AppMethod } from '../../definition/metadata';
|
||||
import type { IOutboundMessageProviders, ProviderMetadata } from '../../definition/outboundComunication';
|
||||
import type { ProxiedApp } from '../ProxiedApp';
|
||||
import type { AppLogStorage } from '../storage';
|
||||
|
||||
export class OutboundMessageProvider {
|
||||
public isRegistered: boolean;
|
||||
|
||||
constructor(
|
||||
public app: ProxiedApp,
|
||||
public provider: IOutboundMessageProviders,
|
||||
) {
|
||||
this.isRegistered = false;
|
||||
}
|
||||
|
||||
public async runGetProviderMetadata(logStorage: AppLogStorage, accessors: AppAccessorManager): Promise<ProviderMetadata> {
|
||||
return this.runTheCode<ProviderMetadata>(AppMethod._OUTBOUND_GET_PROVIDER_METADATA, logStorage, accessors, []);
|
||||
}
|
||||
|
||||
public async runSendOutboundMessage(logStorage: AppLogStorage, accessors: AppAccessorManager, body: any): Promise<void> {
|
||||
await this.runTheCode(AppMethod._OUTBOUND_SEND_MESSAGE, logStorage, accessors, [body]);
|
||||
}
|
||||
|
||||
private async runTheCode<T = unknown>(
|
||||
method: AppMethod._OUTBOUND_GET_PROVIDER_METADATA | AppMethod._OUTBOUND_SEND_MESSAGE,
|
||||
logStorage: AppLogStorage,
|
||||
accessors: AppAccessorManager,
|
||||
runContextArgs: Array<any>,
|
||||
): Promise<T> {
|
||||
const provider = `${this.provider.name}-${this.provider.type}`;
|
||||
|
||||
try {
|
||||
const result = await this.app.getDenoRuntime().sendRequest({
|
||||
method: `outboundCommunication:${provider}:${method}`,
|
||||
params: runContextArgs,
|
||||
});
|
||||
|
||||
return result as T;
|
||||
} catch (e) {
|
||||
if (e?.message === 'error-invalid-provider') {
|
||||
throw new Error('error-provider-not-registered');
|
||||
}
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
import type { AppAccessorManager } from '.';
|
||||
import type {
|
||||
IOutboundMessageProviders,
|
||||
IOutboundEmailMessageProvider,
|
||||
IOutboundPhoneMessageProvider,
|
||||
ValidOutboundProvider,
|
||||
} from '../../definition/outboundComunication';
|
||||
import type { AppManager } from '../AppManager';
|
||||
import type { OutboundMessageBridge } from '../bridges';
|
||||
import { OutboundMessageProvider } from './AppOutboundCommunicationProvider';
|
||||
import { AppPermissionManager } from './AppPermissionManager';
|
||||
import { PermissionDeniedError } from '../errors/PermissionDeniedError';
|
||||
import { AppPermissions } from '../permissions/AppPermissions';
|
||||
|
||||
export class AppOutboundCommunicationProviderManager {
|
||||
private readonly accessors: AppAccessorManager;
|
||||
|
||||
private readonly bridge: OutboundMessageBridge;
|
||||
|
||||
private outboundMessageProviders: Map<string, Map<ValidOutboundProvider, OutboundMessageProvider>>;
|
||||
|
||||
constructor(private readonly manager: AppManager) {
|
||||
this.bridge = this.manager.getBridges().getOutboundMessageBridge();
|
||||
this.accessors = this.manager.getAccessorManager();
|
||||
|
||||
this.outboundMessageProviders = new Map<string, Map<ValidOutboundProvider, OutboundMessageProvider>>();
|
||||
}
|
||||
|
||||
public isAlreadyDefined(providerId: string, providerType: ValidOutboundProvider): boolean {
|
||||
const providersByApp = this.outboundMessageProviders.get(providerId);
|
||||
if (!providersByApp) {
|
||||
return false;
|
||||
}
|
||||
if (!providersByApp.get(providerType)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public addProvider(appId: string, provider: IOutboundMessageProviders): void {
|
||||
const app = this.manager.getOneById(appId);
|
||||
if (!app) {
|
||||
throw new Error('App must exist in order for an outbound provider to be added.');
|
||||
}
|
||||
|
||||
if (!AppPermissionManager.hasPermission(appId, AppPermissions.outboundComms.provide)) {
|
||||
throw new PermissionDeniedError({
|
||||
appId,
|
||||
missingPermissions: [AppPermissions.outboundComms.provide],
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.outboundMessageProviders.has(appId)) {
|
||||
this.outboundMessageProviders.set(appId, new Map<ValidOutboundProvider, OutboundMessageProvider>());
|
||||
}
|
||||
|
||||
this.outboundMessageProviders.get(appId).set(provider.type, new OutboundMessageProvider(app, provider));
|
||||
}
|
||||
|
||||
public async registerProviders(appId: string): Promise<void> {
|
||||
if (!this.outboundMessageProviders.has(appId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const appProviders = this.outboundMessageProviders.get(appId);
|
||||
if (!appProviders) {
|
||||
return;
|
||||
}
|
||||
|
||||
for await (const [, providerInfo] of appProviders) {
|
||||
if (providerInfo.provider.type === 'phone') {
|
||||
await this.registerPhoneProvider(appId, providerInfo.provider);
|
||||
} else if (providerInfo.provider.type === 'email') {
|
||||
await this.registerEmailProvider(appId, providerInfo.provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async unregisterProviders(appId: string): Promise<void> {
|
||||
if (!this.outboundMessageProviders.has(appId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const appProviders = this.outboundMessageProviders.get(appId);
|
||||
for await (const [, providerInfo] of appProviders) {
|
||||
await this.unregisterProvider(appId, providerInfo);
|
||||
}
|
||||
|
||||
this.outboundMessageProviders.delete(appId);
|
||||
}
|
||||
|
||||
private registerPhoneProvider(appId: string, provider: IOutboundPhoneMessageProvider): Promise<void> {
|
||||
return this.bridge.doRegisterPhoneProvider(provider, appId);
|
||||
}
|
||||
|
||||
private registerEmailProvider(appId: string, provider: IOutboundEmailMessageProvider): Promise<void> {
|
||||
return this.bridge.doRegisterEmailProvider(provider, appId);
|
||||
}
|
||||
|
||||
private async unregisterProvider(appId: string, info: OutboundMessageProvider): Promise<void> {
|
||||
const key = info.provider.type;
|
||||
|
||||
await this.bridge.doUnRegisterProvider(info.provider, appId);
|
||||
|
||||
info.isRegistered = false;
|
||||
|
||||
const map = this.outboundMessageProviders.get(appId);
|
||||
if (map) {
|
||||
map.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
public getProviderMetadata(appId: string, providerType: ValidOutboundProvider) {
|
||||
const providerInfo = this.outboundMessageProviders.get(appId)?.get(providerType);
|
||||
if (!providerInfo) {
|
||||
throw new Error('provider-not-registered');
|
||||
}
|
||||
|
||||
return providerInfo.runGetProviderMetadata(this.manager.getLogStorage(), this.accessors);
|
||||
}
|
||||
|
||||
public sendOutboundMessage(appId: string, providerType: ValidOutboundProvider, body: unknown) {
|
||||
const providerInfo = this.outboundMessageProviders.get(appId)?.get(providerType);
|
||||
if (!providerInfo) {
|
||||
throw new Error('provider-not-registered');
|
||||
}
|
||||
|
||||
return providerInfo.runSendOutboundMessage(this.manager.getLogStorage(), this.accessors, body);
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@ import { AppApiManager } from './AppApiManager';
|
||||
import { AppExternalComponentManager } from './AppExternalComponentManager';
|
||||
import { AppLicenseManager } from './AppLicenseManager';
|
||||
import { AppListenerManager } from './AppListenerManager';
|
||||
import { AppOutboundCommunicationProviderManager } from './AppOutboundCommunicationProviderManager';
|
||||
import { AppSchedulerManager } from './AppSchedulerManager';
|
||||
import { AppSettingsManager } from './AppSettingsManager';
|
||||
import { AppSlashCommandManager } from './AppSlashCommandManager';
|
||||
@ -18,4 +19,5 @@ export {
|
||||
AppApiManager,
|
||||
AppSchedulerManager,
|
||||
AppVideoConfProviderManager,
|
||||
AppOutboundCommunicationProviderManager,
|
||||
};
|
||||
|
||||
@ -119,6 +119,9 @@ export const AppPermissions = {
|
||||
read: { name: 'oauth-app.read' },
|
||||
write: { name: 'oauth-app.write' },
|
||||
},
|
||||
'outboundComms': {
|
||||
provide: { name: 'outbound-communication.provide' },
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
AppSettingsManager,
|
||||
AppSlashCommandManager,
|
||||
AppVideoConfProviderManager,
|
||||
AppOutboundCommunicationProviderManager,
|
||||
} from '../../src/server/managers';
|
||||
import type { AppLogStorage, AppMetadataStorage, AppSourceStorage } from '../../src/server/storage';
|
||||
import { SimpleClass, TestInfastructureSetup } from '../test-data/utilities';
|
||||
@ -118,5 +119,6 @@ export class AppManagerTestFixture {
|
||||
Expect(manager.getApiManager() instanceof AppApiManager).toBe(true);
|
||||
Expect(manager.getSettingsManager() instanceof AppSettingsManager).toBe(true);
|
||||
Expect(manager.getVideoConfProviderManager() instanceof AppVideoConfProviderManager).toBe(true);
|
||||
Expect(manager.getOutboundCommunicationProviderManager() instanceof AppOutboundCommunicationProviderManager).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Expect, Setup, SetupFixture, Test } from 'alsatian';
|
||||
|
||||
import type { AppOutboundCommunicationProviderManager } from '../../../server/managers/AppOutboundCommunicationProviderManager';
|
||||
import { AppStatus } from '../../../src/definition/AppStatus';
|
||||
import type { AppMethod } from '../../../src/definition/metadata';
|
||||
import type { AppManager } from '../../../src/server/AppManager';
|
||||
@ -84,7 +85,10 @@ export class AppAccessorsTestFixture {
|
||||
getSettingsManager() {
|
||||
return {} as AppSettingsManager;
|
||||
},
|
||||
} as AppManager;
|
||||
getOutboundCommunicationProviderManager() {
|
||||
return {} as AppOutboundCommunicationProviderManager;
|
||||
},
|
||||
} as unknown as AppManager;
|
||||
|
||||
this.mockAccessors = new AppAccessorManager(this.mockManager);
|
||||
const ac = this.mockAccessors;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { RestorableFunctionSpy } from 'alsatian';
|
||||
import { Expect, Setup, SetupFixture, SpyOn, Teardown, Test } from 'alsatian';
|
||||
|
||||
import type { AppOutboundCommunicationProviderManager } from '../../../server/managers/AppOutboundCommunicationProviderManager';
|
||||
import type { AppManager } from '../../../src/server/AppManager';
|
||||
import type { ProxiedApp } from '../../../src/server/ProxiedApp';
|
||||
import type { AppBridges } from '../../../src/server/bridges';
|
||||
@ -52,7 +53,10 @@ export class AppAccessorManagerTestFixture {
|
||||
getVideoConfProviderManager() {
|
||||
return {} as AppVideoConfProviderManager;
|
||||
},
|
||||
} as AppManager;
|
||||
getOutboundCommunicationProviderManager() {
|
||||
return {} as AppOutboundCommunicationProviderManager;
|
||||
},
|
||||
} as unknown as AppManager;
|
||||
}
|
||||
|
||||
@Setup
|
||||
|
||||
@ -13,6 +13,7 @@ import { TestsInternalFederationBridge } from './internalFederationBridge';
|
||||
import { TestLivechatBridge } from './livechatBridge';
|
||||
import { TestsMessageBridge } from './messageBridge';
|
||||
import { TestsModerationBridge } from './moderationBridge';
|
||||
import { TestOutboundCommunicationBridge } from './outboundComms';
|
||||
import { TestsPersisBridge } from './persisBridge';
|
||||
import { TestsRoleBridge } from './roleBridge';
|
||||
import { TestsRoomBridge } from './roomBridge';
|
||||
@ -35,6 +36,7 @@ import type {
|
||||
LivechatBridge,
|
||||
MessageBridge,
|
||||
ModerationBridge,
|
||||
OutboundMessageBridge,
|
||||
PersistenceBridge,
|
||||
RoleBridge,
|
||||
RoomBridge,
|
||||
@ -102,6 +104,8 @@ export class TestsAppBridges extends AppBridges {
|
||||
|
||||
private readonly threadBridge: ThreadBridge;
|
||||
|
||||
private readonly outboundCommsBridge: TestOutboundCommunicationBridge;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.appDetails = new TestsAppDetailChangesBridge();
|
||||
@ -129,6 +133,7 @@ export class TestsAppBridges extends AppBridges {
|
||||
this.threadBridge = new TestsThreadBridge();
|
||||
this.emailBridge = new TestsEmailBridge();
|
||||
this.contactBridge = new TestContactBridge();
|
||||
this.outboundCommsBridge = new TestOutboundCommunicationBridge();
|
||||
}
|
||||
|
||||
public getCommandBridge(): TestsCommandBridge {
|
||||
@ -234,4 +239,8 @@ export class TestsAppBridges extends AppBridges {
|
||||
public getContactBridge(): ContactBridge {
|
||||
return this.contactBridge;
|
||||
}
|
||||
|
||||
public getOutboundMessageBridge(): OutboundMessageBridge {
|
||||
return this.outboundCommsBridge;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
import type {
|
||||
IOutboundEmailMessageProvider,
|
||||
IOutboundMessageProviders,
|
||||
IOutboundPhoneMessageProvider,
|
||||
} from '@rocket.chat/apps-engine/definition/outboundComunication';
|
||||
import { OutboundMessageBridge } from '@rocket.chat/apps-engine/server/bridges';
|
||||
|
||||
export class TestOutboundCommunicationBridge extends OutboundMessageBridge {
|
||||
protected async registerPhoneProvider(provider: IOutboundPhoneMessageProvider, appId: string): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
protected async registerEmailProvider(provider: IOutboundEmailMessageProvider, appId: string): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
protected async unRegisterProvider(provider: IOutboundMessageProviders, appId: string): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ import { TestsAppBridges } from './bridges/appBridges';
|
||||
import { TestSourceStorage } from './storage/TestSourceStorage';
|
||||
import { TestsAppLogStorage } from './storage/logStorage';
|
||||
import { TestsAppStorage } from './storage/storage';
|
||||
import type { AppOutboundCommunicationProviderManager } from '../../server/managers/AppOutboundCommunicationProviderManager';
|
||||
import { AppStatus } from '../../src/definition/AppStatus';
|
||||
import type { IHttp, IModify, IPersistence, IRead } from '../../src/definition/accessors';
|
||||
import { HttpStatusCode } from '../../src/definition/accessors';
|
||||
@ -109,6 +110,9 @@ export class TestInfastructureSetup {
|
||||
getVideoConfProviderManager() {
|
||||
return {} as AppVideoConfProviderManager;
|
||||
},
|
||||
getOutboundCommunicationProviderManager() {
|
||||
return {} as AppOutboundCommunicationProviderManager;
|
||||
},
|
||||
getSettingsManager() {
|
||||
return {} as AppSettingsManager;
|
||||
},
|
||||
|
||||
@ -1,3 +1,8 @@
|
||||
import type {
|
||||
IOutboundEmailMessageProvider,
|
||||
IOutboundPhoneMessageProvider,
|
||||
} from '@rocket.chat/apps-engine/definition/outboundComunication';
|
||||
|
||||
export interface IOutboundProviderTemplate {
|
||||
id: string;
|
||||
name: string;
|
||||
@ -115,6 +120,18 @@ export type IOutboundProviderMetadata = IOutboundProvider & {
|
||||
templates: Record<string, IOutboundProviderTemplate[]>;
|
||||
};
|
||||
|
||||
export interface IOutboundMessageProvider {
|
||||
registerPhoneProvider(provider: IOutboundPhoneMessageProvider): void;
|
||||
registerEmailProvider(provider: IOutboundEmailMessageProvider): void;
|
||||
getOutboundMessageProviders(type?: ValidOutboundProvider): IOutboundProvider[];
|
||||
unregisterProvider(appId: string, providerType: string): void;
|
||||
}
|
||||
|
||||
export const ValidOutboundProviderList = ['phone', 'email'] as const;
|
||||
|
||||
export type ValidOutboundProvider = (typeof ValidOutboundProviderList)[number];
|
||||
|
||||
export interface IOutboundMessageProviderService {
|
||||
outboundMessageProvider: IOutboundMessageProvider;
|
||||
listOutboundProviders(type?: string): IOutboundProvider[];
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user