Skip to content

Commit fccb537

Browse files
KevLehmanlucas-a-pelegrinosampaiodiego
authored
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>
1 parent 0b4f3d3 commit fccb537

File tree

32 files changed

+525
-17
lines changed

32 files changed

+525
-17
lines changed

apps/meteor/app/apps/server/bridges/bridges.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { AppLivechatBridge } from './livechat';
1616
import { AppMessageBridge } from './messages';
1717
import { AppModerationBridge } from './moderation';
1818
import { AppOAuthAppsBridge } from './oauthApps';
19+
import { OutboundCommunicationBridge } from './outboundCommunication';
1920
import { AppPersistenceBridge } from './persistence';
2021
import { AppRoleBridge } from './roles';
2122
import { AppRoomBridge } from './rooms';
@@ -57,6 +58,7 @@ export class RealAppBridges extends AppBridges {
5758
this._roleBridge = new AppRoleBridge(orch);
5859
this._emailBridge = new AppEmailBridge(orch);
5960
this._contactBridge = new AppContactBridge(orch);
61+
this._outboundMessageBridge = new OutboundCommunicationBridge(orch);
6062
}
6163

6264
getCommandBridge() {
@@ -139,6 +141,10 @@ export class RealAppBridges extends AppBridges {
139141
return this._videoConfBridge;
140142
}
141143

144+
getOutboundMessageBridge() {
145+
return this._outboundMessageBridge;
146+
}
147+
142148
getOAuthAppsBridge() {
143149
return this._oAuthBridge;
144150
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import type { IAppServerOrchestrator } from '@rocket.chat/apps';
2+
import type {
3+
IOutboundEmailMessageProvider,
4+
IOutboundMessageProviders,
5+
IOutboundPhoneMessageProvider,
6+
} from '@rocket.chat/apps-engine/definition/outboundComunication';
7+
import { OutboundMessageBridge } from '@rocket.chat/apps-engine/server/bridges';
8+
9+
import { getOutboundService } from '../../../livechat/server/lib/outboundcommunication';
10+
11+
export class OutboundCommunicationBridge extends OutboundMessageBridge {
12+
constructor(private readonly orch: IAppServerOrchestrator) {
13+
super();
14+
}
15+
16+
protected async registerPhoneProvider(provider: IOutboundPhoneMessageProvider, appId: string): Promise<void> {
17+
try {
18+
this.orch.debugLog(`App ${appId} is registering a phone outbound provider.`);
19+
getOutboundService().outboundMessageProvider.registerPhoneProvider(provider);
20+
} catch (err) {
21+
this.orch.getRocketChatLogger().error({ appId, err, msg: 'Failed to register phone provider' });
22+
throw new Error('error-registering-provider');
23+
}
24+
}
25+
26+
protected async registerEmailProvider(provider: IOutboundEmailMessageProvider, appId: string): Promise<void> {
27+
try {
28+
this.orch.debugLog(`App ${appId} is registering an email outbound provider.`);
29+
getOutboundService().outboundMessageProvider.registerEmailProvider(provider);
30+
} catch (err) {
31+
this.orch.getRocketChatLogger().error({ appId, err, msg: 'Failed to register email provider' });
32+
throw new Error('error-registering-provider');
33+
}
34+
}
35+
36+
protected async unRegisterProvider(provider: IOutboundMessageProviders, appId: string): Promise<void> {
37+
try {
38+
this.orch.debugLog(`App ${appId} is unregistering an outbound provider.`);
39+
getOutboundService().outboundMessageProvider.unregisterProvider(appId, provider.type);
40+
} catch (err) {
41+
this.orch.getRocketChatLogger().error({ appId, err, msg: 'Failed to unregister provider' });
42+
throw new Error('error-unregistering-provider');
43+
}
44+
}
45+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { IOutboundMessageProviderService } from '@rocket.chat/core-typings';
2+
import { makeFunction } from '@rocket.chat/patch-injection';
3+
4+
export const getOutboundService = makeFunction((): IOutboundMessageProviderService => {
5+
throw new Error('error-no-license');
6+
});

apps/meteor/ee/app/livechat-enterprise/server/api/lib/outbound.ts

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
1-
import type { IOutboundProvider, ValidOutboundProvider } from '@rocket.chat/core-typings';
1+
import { Apps } from '@rocket.chat/apps';
2+
import type {
3+
IOutboundProvider,
4+
ValidOutboundProvider,
5+
IOutboundMessageProviderService,
6+
IOutboundProviderMetadata,
7+
} from '@rocket.chat/core-typings';
28
import { ValidOutboundProviderList } from '@rocket.chat/core-typings';
39

10+
import { getOutboundService } from '../../../../../../app/livechat/server/lib/outboundcommunication';
411
import { OutboundMessageProvider } from '../../../../../../server/lib/OutboundMessageProvider';
512

6-
export class OutboundMessageProviderService {
13+
export class OutboundMessageProviderService implements IOutboundMessageProviderService {
714
private readonly provider: OutboundMessageProvider;
815

916
constructor() {
1017
this.provider = new OutboundMessageProvider();
1118
}
1219

20+
get outboundMessageProvider() {
21+
return this.provider;
22+
}
23+
1324
private isProviderValid(type: any): type is ValidOutboundProvider {
1425
return ValidOutboundProviderList.includes(type);
1526
}
@@ -21,6 +32,41 @@ export class OutboundMessageProviderService {
2132

2233
return this.provider.getOutboundMessageProviders(type);
2334
}
35+
36+
public getProviderMetadata(providerId: string): Promise<IOutboundProviderMetadata> {
37+
const provider = this.provider.findOneByProviderId(providerId);
38+
if (!provider) {
39+
throw new Error('error-invalid-provider');
40+
}
41+
42+
return this.getProviderManager().getProviderMetadata(provider.appId, provider.type);
43+
}
44+
45+
private getProviderManager() {
46+
if (!Apps.self?.isLoaded()) {
47+
throw new Error('apps-engine-not-loaded');
48+
}
49+
50+
const manager = Apps.self?.getManager()?.getOutboundCommunicationProviderManager();
51+
if (!manager) {
52+
throw new Error('apps-engine-not-configured-correctly');
53+
}
54+
55+
return manager;
56+
}
57+
58+
public sendMessage(providerId: string, body: any) {
59+
const provider = this.provider.findOneByProviderId(providerId);
60+
if (!provider) {
61+
throw new Error('error-invalid-provider');
62+
}
63+
64+
return this.getProviderManager().sendOutboundMessage(provider.appId, provider.type, body);
65+
}
2466
}
2567

2668
export const outboundMessageProvider = new OutboundMessageProviderService();
69+
70+
getOutboundService.patch(() => {
71+
return outboundMessageProvider;
72+
});

apps/meteor/ee/app/livechat-enterprise/server/api/outbound.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ const outboundCommsEndpoints = API.v1.get(
3030
providerType: {
3131
type: 'string',
3232
},
33+
documentationUrl: {
34+
type: 'string',
35+
},
3336
},
3437
},
3538
},

apps/meteor/server/lib/OutboundMessageProvider.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,7 @@ import type {
33
IOutboundMessageProviders,
44
IOutboundPhoneMessageProvider,
55
} from '@rocket.chat/apps-engine/definition/outboundComunication';
6-
import type { ValidOutboundProvider, IOutboundProvider } from '@rocket.chat/core-typings';
7-
8-
interface IOutboundMessageProvider {
9-
registerPhoneProvider(provider: IOutboundPhoneMessageProvider): void;
10-
registerEmailProvider(provider: IOutboundEmailMessageProvider): void;
11-
getOutboundMessageProviders(type?: ValidOutboundProvider): IOutboundProvider[];
12-
unregisterProvider(appId: string, providerType: string): void;
13-
}
6+
import type { ValidOutboundProvider, IOutboundProvider, IOutboundMessageProvider } from '@rocket.chat/core-typings';
147

158
export class OutboundMessageProvider implements IOutboundMessageProvider {
169
private readonly outboundMessageProviders: Map<ValidOutboundProvider, IOutboundMessageProviders[]>;
@@ -22,6 +15,17 @@ export class OutboundMessageProvider implements IOutboundMessageProvider {
2215
]);
2316
}
2417

18+
public findOneByProviderId(providerId: string) {
19+
for (const providers of this.outboundMessageProviders.values()) {
20+
for (const provider of providers) {
21+
if (provider.appId === providerId) {
22+
return provider;
23+
}
24+
}
25+
}
26+
return undefined;
27+
}
28+
2529
public registerPhoneProvider(provider: IOutboundPhoneMessageProvider): void {
2630
this.outboundMessageProviders.set('phone', [...(this.outboundMessageProviders.get('phone') || []), provider]);
2731
}
@@ -36,6 +40,7 @@ export class OutboundMessageProvider implements IOutboundMessageProvider {
3640
providerId: provider.appId,
3741
providerName: provider.name,
3842
providerType: provider.type,
43+
...(provider.documentationUrl && { documentationUrl: provider.documentationUrl }),
3944
...(provider.supportsTemplates && { supportsTemplates: provider.supportsTemplates }),
4045
}));
4146
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { JsonRpcError, Defined } from 'jsonrpc-lite';
2+
import type { IOutboundMessageProviders } from '@rocket.chat/apps-engine/definition/outboundCommunication/IOutboundCommsProvider.ts';
3+
4+
import { AppObjectRegistry } from '../AppObjectRegistry.ts';
5+
import { AppAccessorsInstance } from '../lib/accessors/mod.ts';
6+
import { Logger } from '../lib/logger.ts';
7+
8+
export default async function outboundMessageHandler(call: string, params: unknown): Promise<JsonRpcError | Defined> {
9+
const [, providerName, methodName] = call.split(':');
10+
const provider = AppObjectRegistry.get<IOutboundMessageProviders>(`outboundCommunication:${providerName}`);
11+
if (!provider) {
12+
return new JsonRpcError('error-invalid-provider', -32000);
13+
}
14+
const method = provider[methodName as keyof IOutboundMessageProviders];
15+
const logger = AppObjectRegistry.get<Logger>('logger');
16+
const args = (params as Array<unknown>) ?? [];
17+
18+
try {
19+
logger?.debug(`Executing ${methodName} on outbound communication provider...`);
20+
21+
// deno-lint-ignore ban-types
22+
return await (method as Function).apply(provider, [
23+
...args,
24+
AppAccessorsInstance.getReader(),
25+
AppAccessorsInstance.getModifier(),
26+
AppAccessorsInstance.getHttp(),
27+
AppAccessorsInstance.getPersistence(),
28+
]);
29+
} catch (e) {
30+
return new JsonRpcError(e.message, -32000);
31+
}
32+
}

packages/apps-engine/deno-runtime/lib/accessors/mod.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ import type { ISlashCommand } from '@rocket.chat/apps-engine/definition/slashcom
1313
import type { IProcessor } from '@rocket.chat/apps-engine/definition/scheduler/IProcessor.ts';
1414
import type { IApi } from '@rocket.chat/apps-engine/definition/api/IApi.ts';
1515
import type { IVideoConfProvider } from '@rocket.chat/apps-engine/definition/videoConfProviders/IVideoConfProvider.ts';
16+
import type {
17+
IOutboundPhoneMessageProvider,
18+
IOutboundEmailMessageProvider,
19+
} from '@rocket.chat/apps-engine/definition/outboundCommunication/IOutboundCommsProvider.ts';
1620

1721
import { Http } from './http.ts';
1822
import { HttpExtend } from './extenders/HttpExtender.ts';
@@ -188,6 +192,17 @@ export class AppAccessors {
188192
return this._proxy.provideVideoConfProvider(provider);
189193
},
190194
},
195+
outboundCommunication: {
196+
_proxy: this.proxify('getConfigurationExtend:outboundCommunication'),
197+
registerEmailProvider(provider: IOutboundEmailMessageProvider) {
198+
AppObjectRegistry.set(`outboundCommunication:${provider.name}-${provider.type}`, provider);
199+
return this._proxy.registerEmailProvider(provider);
200+
},
201+
registerPhoneProvider(provider: IOutboundPhoneMessageProvider) {
202+
AppObjectRegistry.set(`outboundCommunication:${provider.name}-${provider.type}`, provider);
203+
return this._proxy.registerPhoneProvider(provider);
204+
},
205+
},
191206
slashCommands: {
192207
_proxy: this.proxify('getConfigurationExtend:slashCommands'),
193208
provideSlashCommand(slashcommand: ISlashCommand) {

packages/apps-engine/deno-runtime/main.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ import handleApp from './handlers/app/handler.ts';
2323
import handleScheduler from './handlers/scheduler-handler.ts';
2424
import registerErrorListeners from './error-handlers.ts';
2525
import { sendMetrics } from './lib/metricsCollector.ts';
26+
import outboundMessageHandler from './handlers/outboundcomms-handler.ts';
2627

2728
type Handlers = {
2829
app: typeof handleApp;
2930
api: typeof apiHandler;
3031
slashcommand: typeof slashcommandHandler;
3132
videoconference: typeof videoConferenceHandler;
33+
outboundCommunication: typeof outboundMessageHandler;
3234
scheduler: typeof handleScheduler;
3335
ping: (method: string, params: unknown) => 'pong';
3436
};
@@ -41,6 +43,7 @@ async function requestRouter({ type, payload }: Messenger.JsonRpcRequest): Promi
4143
api: apiHandler,
4244
slashcommand: slashcommandHandler,
4345
videoconference: videoConferenceHandler,
46+
outboundCommunication: outboundMessageHandler,
4447
scheduler: handleScheduler,
4548
ping: (_method, _params) => 'pong',
4649
};

packages/apps-engine/src/definition/accessors/IConfigurationExtend.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { IApiExtend } from './IApiExtend';
22
import type { IExternalComponentsExtend } from './IExternalComponentsExtend';
33
import type { IHttpExtend } from './IHttp';
4+
import type { IOutboundCommunicationProviderExtend } from './IOutboundCommunicationProviderExtend';
45
import type { ISchedulerExtend } from './ISchedulerExtend';
56
import type { ISettingsExtend } from './ISettingsExtend';
67
import type { ISlashCommandsExtend } from './ISlashCommandsExtend';
@@ -33,4 +34,7 @@ export interface IConfigurationExtend {
3334

3435
/** Accessor for declaring the videoconf providers which your App provides. */
3536
readonly videoConfProviders: IVideoConfProvidersExtend;
37+
38+
/** Accessor for declaring outbound communication providers */
39+
readonly outboundCommunication: IOutboundCommunicationProviderExtend;
3640
}

0 commit comments

Comments
 (0)