mirror of
https://github.com/RocketChat/Rocket.Chat.git
synced 2025-12-28 06:47:25 +00:00
feat: Outbound Comms endpoints (#36377)
Co-authored-by: Kevin Aleman <11577696+KevLehman@users.noreply.github.com>
This commit is contained in:
parent
7d153619fc
commit
2cec8acd5b
7
.changeset/new-mails-rhyme.md
Normal file
7
.changeset/new-mails-rhyme.md
Normal file
@ -0,0 +1,7 @@
|
||||
---
|
||||
"@rocket.chat/meteor": minor
|
||||
"@rocket.chat/apps-engine": minor
|
||||
"@rocket.chat/core-typings": minor
|
||||
---
|
||||
|
||||
Adds new endpoints for outbound communications
|
||||
@ -1,10 +1,11 @@
|
||||
import type { ValidOutboundProvider } from '@rocket.chat/core-typings';
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
|
||||
import ContactInfoDetailsEntry from './ContactInfoDetailsEntry';
|
||||
import { parseOutboundPhoneNumber } from '../../../../../lib/voip/parseOutboundPhoneNumber';
|
||||
|
||||
type ContactInfoDetailsGroupProps = {
|
||||
type: 'phone' | 'email';
|
||||
type: ValidOutboundProvider;
|
||||
label: string;
|
||||
values: string[];
|
||||
};
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
import type { IOutboundProvider, ValidOutboundProvider } from '@rocket.chat/core-typings';
|
||||
import { ValidOutboundProviderList } from '@rocket.chat/core-typings';
|
||||
|
||||
import { OutboundMessageProvider } from '../../../../../../server/lib/OutboundMessageProvider';
|
||||
|
||||
export class OutboundMessageProviderService {
|
||||
private readonly provider: OutboundMessageProvider;
|
||||
|
||||
constructor() {
|
||||
this.provider = new OutboundMessageProvider();
|
||||
}
|
||||
|
||||
private isProviderValid(type: any): type is ValidOutboundProvider {
|
||||
return ValidOutboundProviderList.includes(type);
|
||||
}
|
||||
|
||||
public listOutboundProviders(type?: string): IOutboundProvider[] {
|
||||
if (type !== undefined && !this.isProviderValid(type)) {
|
||||
throw new Error('Invalid type');
|
||||
}
|
||||
|
||||
return this.provider.getOutboundMessageProviders(type);
|
||||
}
|
||||
}
|
||||
|
||||
export const outboundMessageProvider = new OutboundMessageProviderService();
|
||||
@ -0,0 +1,49 @@
|
||||
import type { IOutboundProvider } from '@rocket.chat/core-typings';
|
||||
import { ajv } from '@rocket.chat/rest-typings/src/v1/Ajv';
|
||||
|
||||
import { API } from '../../../../../app/api/server';
|
||||
import { isGETOutboundProviderParams } from '../outboundcomms/rest';
|
||||
import { outboundMessageProvider } from './lib/outbound';
|
||||
import type { ExtractRoutesFromAPI } from '../../../../../app/api/server/ApiClass';
|
||||
|
||||
const outboundCommsEndpoints = API.v1.get(
|
||||
'omnichannel/outbound/providers',
|
||||
{
|
||||
response: {
|
||||
200: ajv.compile<{ providers: IOutboundProvider[] }>({
|
||||
providers: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
providerId: {
|
||||
type: 'string',
|
||||
},
|
||||
providerName: {
|
||||
type: 'string',
|
||||
},
|
||||
supportsTemplates: {
|
||||
type: 'boolean',
|
||||
},
|
||||
providerType: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
query: isGETOutboundProviderParams,
|
||||
authRequired: true,
|
||||
},
|
||||
async function action() {
|
||||
const { type } = this.queryParams;
|
||||
|
||||
const providers = outboundMessageProvider.listOutboundProviders(type);
|
||||
return API.v1.success({
|
||||
providers,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export type OutboundCommsEndpoints = ExtractRoutesFromAPI<typeof outboundCommsEndpoints>;
|
||||
@ -1,24 +1,15 @@
|
||||
import type { IOutboundProvider, IOutboundMessage, IOutboundProviderMetadata } from '@rocket.chat/core-typings';
|
||||
import type { IOutboundMessage } from '@rocket.chat/core-typings';
|
||||
import Ajv from 'ajv';
|
||||
|
||||
import type { OutboundCommsEndpoints } from '../api/outbound';
|
||||
|
||||
const ajv = new Ajv({
|
||||
coerceTypes: true,
|
||||
});
|
||||
|
||||
declare module '@rocket.chat/rest-typings' {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
interface Endpoints {
|
||||
'/v1/omnichannel/outbound/providers': {
|
||||
GET: (params: GETOutboundProviderParams) => IOutboundProvider[];
|
||||
};
|
||||
'/v1/omnichannel/outbound/providers/:id/metadata': {
|
||||
GET: () => IOutboundProviderMetadata;
|
||||
};
|
||||
'/v1/omnichannel/outbound/providers/:id/message': {
|
||||
// Note: we may need to adapt this type when the API is implemented and UI starts to use it
|
||||
POST: (params: POSTOutboundMessageParams) => void;
|
||||
};
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
|
||||
interface Endpoints extends OutboundCommsEndpoints {}
|
||||
}
|
||||
|
||||
type GETOutboundProviderParams = { type?: string };
|
||||
|
||||
@ -3,16 +3,17 @@ 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?: 'phone' | 'email'): IOutboundMessageProviders[];
|
||||
getOutboundMessageProviders(type?: ValidOutboundProvider): IOutboundProvider[];
|
||||
unregisterProvider(appId: string, providerType: string): void;
|
||||
}
|
||||
|
||||
export class OutboundMessageProvider implements IOutboundMessageProvider {
|
||||
private readonly outboundMessageProviders: Map<'phone' | 'email', IOutboundMessageProviders[]>;
|
||||
private readonly outboundMessageProviders: Map<ValidOutboundProvider, IOutboundMessageProviders[]>;
|
||||
|
||||
constructor() {
|
||||
this.outboundMessageProviders = new Map([
|
||||
@ -29,15 +30,27 @@ export class OutboundMessageProvider implements IOutboundMessageProvider {
|
||||
this.outboundMessageProviders.set('email', [...(this.outboundMessageProviders.get('email') || []), provider]);
|
||||
}
|
||||
|
||||
public getOutboundMessageProviders(type?: 'phone' | 'email'): IOutboundMessageProviders[] {
|
||||
public getOutboundMessageProviders(type?: ValidOutboundProvider): IOutboundProvider[] {
|
||||
if (type) {
|
||||
return Array.from(this.outboundMessageProviders.get(type)?.values() || []);
|
||||
return Array.from(this.outboundMessageProviders.get(type)?.values() || []).map((provider) => ({
|
||||
providerId: provider.appId,
|
||||
providerName: provider.name,
|
||||
providerType: provider.type,
|
||||
...(provider.supportsTemplates && { supportsTemplates: provider.supportsTemplates }),
|
||||
}));
|
||||
}
|
||||
|
||||
return Array.from(this.outboundMessageProviders.values()).flatMap((providers) => providers);
|
||||
return Array.from(this.outboundMessageProviders.values())
|
||||
.flatMap((providers) => providers)
|
||||
.map((provider) => ({
|
||||
providerId: provider.appId,
|
||||
providerName: provider.name,
|
||||
supportsTemplates: provider.supportsTemplates,
|
||||
providerType: provider.type,
|
||||
}));
|
||||
}
|
||||
|
||||
public unregisterProvider(appId: string, providerType: 'phone' | 'email'): void {
|
||||
public unregisterProvider(appId: string, providerType: ValidOutboundProvider): void {
|
||||
const providers = this.outboundMessageProviders.get(providerType);
|
||||
if (!providers) {
|
||||
return;
|
||||
|
||||
@ -3,6 +3,7 @@ import type {
|
||||
IOutboundPhoneMessageProvider,
|
||||
} from '@rocket.chat/apps-engine/definition/outboundComunication';
|
||||
import { expect } from 'chai';
|
||||
import { describe, it, beforeEach } from 'mocha';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import { OutboundMessageProvider } from '../../../../server/lib/OutboundMessageProvider';
|
||||
@ -28,7 +29,11 @@ describe('OutboundMessageProvider', () => {
|
||||
const providers = outboundMessageProvider.getOutboundMessageProviders('phone');
|
||||
|
||||
expect(providers).to.have.lengthOf(1);
|
||||
expect(providers[0]).to.deep.equal(phoneProvider);
|
||||
expect(providers[0]).to.deep.equal({
|
||||
providerId: '123',
|
||||
providerName: 'Test Phone Provider',
|
||||
providerType: 'phone',
|
||||
});
|
||||
});
|
||||
|
||||
it('should successfully register a email provider', () => {
|
||||
@ -44,7 +49,11 @@ describe('OutboundMessageProvider', () => {
|
||||
const providers = outboundMessageProvider.getOutboundMessageProviders('email');
|
||||
|
||||
expect(providers).to.have.lengthOf(1);
|
||||
expect(providers[0]).to.deep.equal(emailProvider);
|
||||
expect(providers[0]).to.deep.equal({
|
||||
providerId: '123',
|
||||
providerName: 'Test Email Provider',
|
||||
providerType: 'email',
|
||||
});
|
||||
});
|
||||
|
||||
it('should list currently registered providers [unfiltered]', () => {
|
||||
@ -69,8 +78,8 @@ describe('OutboundMessageProvider', () => {
|
||||
const providers = outboundMessageProvider.getOutboundMessageProviders();
|
||||
|
||||
expect(providers).to.have.lengthOf(2);
|
||||
expect(providers.some((provider) => provider.type === 'phone')).to.be.true;
|
||||
expect(providers.some((provider) => provider.type === 'email')).to.be.true;
|
||||
expect(providers.some((provider) => provider.providerType === 'phone')).to.be.true;
|
||||
expect(providers.some((provider) => provider.providerType === 'email')).to.be.true;
|
||||
});
|
||||
|
||||
it('should list currently registered providers [filtered by type]', () => {
|
||||
@ -95,7 +104,7 @@ describe('OutboundMessageProvider', () => {
|
||||
const providers = outboundMessageProvider.getOutboundMessageProviders('phone');
|
||||
|
||||
expect(providers).to.have.lengthOf(1);
|
||||
expect(providers[0].type).to.equal('phone');
|
||||
expect(providers[0].providerType).to.equal('phone');
|
||||
});
|
||||
|
||||
it('should unregister a provider', () => {
|
||||
@ -127,6 +136,6 @@ describe('OutboundMessageProvider', () => {
|
||||
registeredProviders = outboundMessageProvider.getOutboundMessageProviders('phone');
|
||||
|
||||
expect(registeredProviders).to.have.lengthOf(1);
|
||||
expect(registeredProviders.some((provider) => provider.appId !== '123')).to.be.true;
|
||||
expect(registeredProviders.some((provider) => provider.providerId !== '123')).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
@ -13,6 +13,7 @@ interface IOutboundMessageProviderBase {
|
||||
appId: string;
|
||||
name: string;
|
||||
documentationUrl?: string;
|
||||
supportsTemplates?: boolean;
|
||||
sendOutboundMessage(message: IOutboundMessage): Promise<void>;
|
||||
}
|
||||
|
||||
|
||||
@ -107,10 +107,14 @@ type TemplateParameter =
|
||||
export type IOutboundProvider = {
|
||||
providerId: string;
|
||||
providerName: string;
|
||||
supportsTemplates: boolean;
|
||||
supportsTemplates?: boolean;
|
||||
providerType: 'phone' | 'email';
|
||||
};
|
||||
|
||||
export type IOutboundProviderMetadata = IOutboundProvider & {
|
||||
templates: Record<string, IOutboundProviderTemplate[]>;
|
||||
};
|
||||
|
||||
export const ValidOutboundProviderList = ['phone', 'email'] as const;
|
||||
|
||||
export type ValidOutboundProvider = (typeof ValidOutboundProviderList)[number];
|
||||
|
||||
Loading…
Reference in New Issue
Block a user