diff --git a/.changeset/small-mangos-hang.md b/.changeset/small-mangos-hang.md new file mode 100644 index 0000000000000..75def9c2b46d4 --- /dev/null +++ b/.changeset/small-mangos-hang.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/apps-engine": minor +--- + +Creates a new endpoint that allows agents to send an outbound message from a registered app provider diff --git a/apps/meteor/ee/app/livechat-enterprise/server/api/lib/outbound.ts b/apps/meteor/ee/app/livechat-enterprise/server/api/lib/outbound.ts index 12f8c06b88857..f73602f34b43a 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/api/lib/outbound.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/api/lib/outbound.ts @@ -4,6 +4,7 @@ import type { ValidOutboundProvider, IOutboundMessageProviderService, IOutboundProviderMetadata, + IOutboundMessage, } from '@rocket.chat/core-typings'; import { ValidOutboundProviderList } from '@rocket.chat/core-typings'; @@ -55,13 +56,13 @@ export class OutboundMessageProviderService implements IOutboundMessageProviderS return manager; } - public sendMessage(providerId: string, body: any) { + public sendMessage(providerId: string, message: IOutboundMessage) { const provider = this.provider.findOneByProviderId(providerId); if (!provider) { throw new Error('error-invalid-provider'); } - return this.getProviderManager().sendOutboundMessage(provider.appId, provider.type, body); + return this.getProviderManager().sendOutboundMessage(provider.appId, provider.type, message); } } diff --git a/apps/meteor/ee/app/livechat-enterprise/server/api/outbound.ts b/apps/meteor/ee/app/livechat-enterprise/server/api/outbound.ts index 92ef41a678c36..6dffaeab93833 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/api/outbound.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/api/outbound.ts @@ -4,6 +4,9 @@ import { GETOutboundProviderParamsSchema, GETOutboundProviderBadRequestErrorSchema, GETOutboundProviderMetadataSchema, + POSTOutboundMessageParams, + POSTOutboundMessageErrorSchema, + POSTOutboundMessageSuccessSchema, } from '../outboundcomms/rest'; import { outboundMessageProvider } from './lib/outbound'; import type { ExtractRoutesFromAPI } from '../../../../../app/api/server/ApiClass'; @@ -45,6 +48,20 @@ const outboundCommsEndpoints = API.v1 metadata: providerMetadata, }); }, + ) + .post( + 'omnichannel/outbound/providers/:id/message', + { + response: { 200: POSTOutboundMessageSuccessSchema, 400: POSTOutboundMessageErrorSchema }, + authRequired: true, + body: POSTOutboundMessageParams, + }, + async function action() { + const { id } = this.urlParams; + + await outboundMessageProvider.sendMessage(id, this.bodyParams); + return API.v1.success(); + }, ); export type OutboundCommsEndpoints = ExtractRoutesFromAPI; diff --git a/apps/meteor/ee/app/livechat-enterprise/server/outboundcomms/rest.ts b/apps/meteor/ee/app/livechat-enterprise/server/outboundcomms/rest.ts index e13d368bea98b..2f7ad05abc40b 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/outboundcomms/rest.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/outboundcomms/rest.ts @@ -1,4 +1,4 @@ -import type { IOutboundMessage, IOutboundProvider, IOutboundProviderMetadata } from '@rocket.chat/core-typings'; +import type { IOutboundMessage, IOutboundProvider, IOutboundProviderMetadata, ValidOutboundProvider } from '@rocket.chat/core-typings'; import Ajv from 'ajv'; import type { OutboundCommsEndpoints } from '../api/outbound'; @@ -12,7 +12,9 @@ declare module '@rocket.chat/rest-typings' { interface Endpoints extends OutboundCommsEndpoints {} } -type GETOutboundProviderParams = { type?: string }; +type GenericErrorResponse = { success: boolean; message: string }; + +type GETOutboundProviderParams = { type?: ValidOutboundProvider }; const GETOutboundProviderSchema = { type: 'object', properties: { @@ -64,18 +66,15 @@ const GETOutboundProviderBadRequestError = { }, }, }; -export const GETOutboundProviderBadRequestErrorSchema = ajv.compile<{ success: boolean; message: string }>( - GETOutboundProviderBadRequestError, -); +export const GETOutboundProviderBadRequestErrorSchema = ajv.compile(GETOutboundProviderBadRequestError); + +type POSTOutboundMessageParamsType = IOutboundMessage; -type POSTOutboundMessageParams = { - message: IOutboundMessage; -}; const POSTOutboundMessageSchema = { type: 'object', required: ['to', 'type'], properties: { - to: { type: 'string' }, + to: { type: 'string', minLength: 1 }, type: { type: 'string' }, templateProviderPhoneNumber: { type: 'string' }, template: { @@ -183,7 +182,30 @@ const POSTOutboundMessageSchema = { additionalProperties: false, }; -export const isPOSTOutboundMessageParams = ajv.compile(POSTOutboundMessageSchema); +export const POSTOutboundMessageParams = ajv.compile(POSTOutboundMessageSchema); + +const POSTOutboundMessageError = { + type: 'object', + properties: { + success: { + type: 'boolean', + }, + message: { + type: 'string', + }, + }, + additionalProperties: false, +}; + +export const POSTOutboundMessageErrorSchema = ajv.compile(POSTOutboundMessageError); + +const POSTOutboundMessageSuccess = { + type: 'object', + properties: {}, + additionalProperties: false, +}; + +export const POSTOutboundMessageSuccessSchema = ajv.compile(POSTOutboundMessageSuccess); const OutboundProviderMetadataSchema = { type: 'object', diff --git a/packages/apps-engine/src/definition/outboundComunication/IOutboundMessage.ts b/packages/apps-engine/src/definition/outboundComunication/IOutboundMessage.ts index 9c177b4501897..9f8f7e3efeca9 100644 --- a/packages/apps-engine/src/definition/outboundComunication/IOutboundMessage.ts +++ b/packages/apps-engine/src/definition/outboundComunication/IOutboundMessage.ts @@ -14,12 +14,12 @@ export interface IOutboundMessage { }; } -type TemplateComponent = { +export type TemplateComponent = { type: 'header' | 'body' | 'footer' | 'button'; parameters: TemplateParameter[]; }; -type TemplateParameter = +export type TemplateParameter = | { type: 'text'; text: string; diff --git a/packages/apps-engine/src/definition/outboundComunication/IOutboundProviderTemplate.ts b/packages/apps-engine/src/definition/outboundComunication/IOutboundProviderTemplate.ts index 6011160c42022..aaaabbf5999a3 100644 --- a/packages/apps-engine/src/definition/outboundComunication/IOutboundProviderTemplate.ts +++ b/packages/apps-engine/src/definition/outboundComunication/IOutboundProviderTemplate.ts @@ -23,7 +23,7 @@ export interface IOutboundProviderTemplate { partnerId: string; externalId: string; updatedExternal: string; // ISO 8601 timestamp - rejectedReason: string | null; + rejectedReason: string | undefined; } type Component = IHeaderComponent | IBodyComponent | IFooterComponent; diff --git a/packages/apps-engine/src/server/errors/AppOutboundProcessError.ts b/packages/apps-engine/src/server/errors/AppOutboundProcessError.ts new file mode 100644 index 0000000000000..a7e905ae90b90 --- /dev/null +++ b/packages/apps-engine/src/server/errors/AppOutboundProcessError.ts @@ -0,0 +1,12 @@ +export class AppOutboundProcessError implements Error { + public name = 'OutboundProviderError'; + + public message: string; + + constructor(message: string, where?: string) { + this.message = message; + if (where) { + this.message += ` (${where})`; + } + } +} diff --git a/packages/apps-engine/src/server/managers/AppOutboundCommunicationProvider.ts b/packages/apps-engine/src/server/managers/AppOutboundCommunicationProvider.ts index dc7495785d926..cc601ae298fa2 100644 --- a/packages/apps-engine/src/server/managers/AppOutboundCommunicationProvider.ts +++ b/packages/apps-engine/src/server/managers/AppOutboundCommunicationProvider.ts @@ -1,7 +1,8 @@ import type { AppAccessorManager } from '.'; import { AppMethod } from '../../definition/metadata'; -import type { IOutboundMessageProviders, ProviderMetadata } from '../../definition/outboundComunication'; +import type { IOutboundMessage, IOutboundMessageProviders, ProviderMetadata } from '../../definition/outboundComunication'; import type { ProxiedApp } from '../ProxiedApp'; +import { AppOutboundProcessError } from '../errors/AppOutboundProcessError'; import type { AppLogStorage } from '../storage'; export class OutboundMessageProvider { @@ -18,7 +19,7 @@ export class OutboundMessageProvider { return this.runTheCode(AppMethod._OUTBOUND_GET_PROVIDER_METADATA, logStorage, accessors, []); } - public async runSendOutboundMessage(logStorage: AppLogStorage, accessors: AppAccessorManager, body: any): Promise { + public async runSendOutboundMessage(logStorage: AppLogStorage, accessors: AppAccessorManager, body: IOutboundMessage): Promise { await this.runTheCode(AppMethod._OUTBOUND_SEND_MESSAGE, logStorage, accessors, [body]); } @@ -41,7 +42,7 @@ export class OutboundMessageProvider { if (e?.message === 'error-invalid-provider') { throw new Error('error-provider-not-registered'); } - console.error(e); + throw new AppOutboundProcessError(e.message, method); } } } diff --git a/packages/apps-engine/src/server/managers/AppOutboundCommunicationProviderManager.ts b/packages/apps-engine/src/server/managers/AppOutboundCommunicationProviderManager.ts index e5fa8a4077fc5..9abfc9280c4c4 100644 --- a/packages/apps-engine/src/server/managers/AppOutboundCommunicationProviderManager.ts +++ b/packages/apps-engine/src/server/managers/AppOutboundCommunicationProviderManager.ts @@ -4,6 +4,7 @@ import type { IOutboundEmailMessageProvider, IOutboundPhoneMessageProvider, ValidOutboundProvider, + IOutboundMessage, } from '../../definition/outboundComunication'; import type { AppManager } from '../AppManager'; import type { OutboundMessageBridge } from '../bridges'; @@ -119,7 +120,7 @@ export class AppOutboundCommunicationProviderManager { return providerInfo.runGetProviderMetadata(this.manager.getLogStorage(), this.accessors); } - public sendOutboundMessage(appId: string, providerType: ValidOutboundProvider, body: unknown) { + public sendOutboundMessage(appId: string, providerType: ValidOutboundProvider, body: IOutboundMessage) { const providerInfo = this.outboundMessageProviders.get(appId)?.get(providerType); if (!providerInfo) { throw new Error('provider-not-registered'); diff --git a/packages/core-typings/src/omnichannel/outbound.ts b/packages/core-typings/src/omnichannel/outbound.ts index afd85f7884707..78e3115c1e86e 100644 --- a/packages/core-typings/src/omnichannel/outbound.ts +++ b/packages/core-typings/src/omnichannel/outbound.ts @@ -27,7 +27,7 @@ export interface IOutboundProviderTemplate { partnerId: string; externalId: string; updatedExternal: string; // ISO 8601 timestamp - rejectedReason: string | null; + rejectedReason: string | undefined; } type Component = IHeaderComponent | IBodyComponent | IFooterComponent; @@ -73,12 +73,12 @@ export interface IOutboundMessage { }; } -type TemplateComponent = { +export type TemplateComponent = { type: 'header' | 'body' | 'footer' | 'button'; parameters: TemplateParameter[]; }; -type TemplateParameter = +export type TemplateParameter = | { type: 'text'; text: string;