@rocket.chat/apps-engine
Version:
The engine code for the Rocket.Chat Apps which manages, runs, translates, coordinates and all of that.
304 lines (258 loc) • 13.7 kB
text/typescript
import type { IAppAccessors } from '@rocket.chat/apps-engine/definition/accessors/IAppAccessors.ts';
import type { IApiEndpointMetadata } from '@rocket.chat/apps-engine/definition/api/IApiEndpointMetadata.ts';
import type { IEnvironmentWrite } from '@rocket.chat/apps-engine/definition/accessors/IEnvironmentWrite.ts';
import type { IEnvironmentRead } from '@rocket.chat/apps-engine/definition/accessors/IEnvironmentRead.ts';
import type { IConfigurationModify } from '@rocket.chat/apps-engine/definition/accessors/IConfigurationModify.ts';
import type { IRead } from '@rocket.chat/apps-engine/definition/accessors/IRead.ts';
import type { IModify } from '@rocket.chat/apps-engine/definition/accessors/IModify.ts';
import type { INotifier } from '@rocket.chat/apps-engine/definition/accessors/INotifier.ts';
import type { IPersistence } from '@rocket.chat/apps-engine/definition/accessors/IPersistence.ts';
import type { IHttp, IHttpExtend } from '@rocket.chat/apps-engine/definition/accessors/IHttp.ts';
import type { IConfigurationExtend } from '@rocket.chat/apps-engine/definition/accessors/IConfigurationExtend.ts';
import type { ISlashCommand } from '@rocket.chat/apps-engine/definition/slashcommands/ISlashCommand.ts';
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 { Http } from './http.ts';
import { HttpExtend } from './extenders/HttpExtender.ts';
import * as Messenger from '../messenger.ts';
import { AppObjectRegistry } from '../../AppObjectRegistry.ts';
import { ModifyCreator } from './modify/ModifyCreator.ts';
import { ModifyUpdater } from './modify/ModifyUpdater.ts';
import { ModifyExtender } from './modify/ModifyExtender.ts';
import { Notifier } from './notifier.ts';
const httpMethods = ['get', 'post', 'put', 'delete', 'head', 'options', 'patch'] as const;
// We need to create this object first thing, as we'll handle references to it later on
if (!AppObjectRegistry.has('apiEndpoints')) {
AppObjectRegistry.set('apiEndpoints', []);
}
export class AppAccessors {
private defaultAppAccessors?: IAppAccessors;
private environmentRead?: IEnvironmentRead;
private environmentWriter?: IEnvironmentWrite;
private configModifier?: IConfigurationModify;
private configExtender?: IConfigurationExtend;
private reader?: IRead;
private modifier?: IModify;
private persistence?: IPersistence;
private creator?: ModifyCreator;
private updater?: ModifyUpdater;
private extender?: ModifyExtender;
private httpExtend: IHttpExtend = new HttpExtend();
private http?: IHttp;
private notifier?: INotifier;
private proxify: <T>(namespace: string, overrides?: Record<string, (...args: unknown[]) => unknown>) => T;
constructor(private readonly senderFn: typeof Messenger.sendRequest) {
this.proxify = <T>(namespace: string, overrides: Record<string, (...args: unknown[]) => unknown> = {}): T =>
new Proxy(
{ __kind: `accessor:${namespace}` },
{
get:
(_target: unknown, prop: string) =>
(...params: unknown[]) => {
// We don't want to send a request for this prop
if (prop === 'toJSON') {
return {};
}
// If the prop is inteded to be overriden by the caller
if (prop in overrides) {
return overrides[prop].apply(undefined, params);
}
return senderFn({
method: `accessor:${namespace}:${prop}`,
params,
})
.then((response) => response.result)
.catch((err) => { throw new Error(err.error) });
},
},
) as T;
this.http = new Http(this.getReader(), this.getPersistence(), this.httpExtend, this.getSenderFn());
this.notifier = new Notifier(this.getSenderFn());
}
public getSenderFn() {
return this.senderFn;
}
public getEnvironmentRead(): IEnvironmentRead {
if (!this.environmentRead) {
this.environmentRead = {
getSettings: () => this.proxify('getEnvironmentRead:getSettings'),
getServerSettings: () => this.proxify('getEnvironmentRead:getServerSettings'),
getEnvironmentVariables: () => this.proxify('getEnvironmentRead:getEnvironmentVariables'),
};
}
return this.environmentRead;
}
public getEnvironmentWrite() {
if (!this.environmentWriter) {
this.environmentWriter = {
getSettings: () => this.proxify('getEnvironmentWrite:getSettings'),
getServerSettings: () => this.proxify('getEnvironmentWrite:getServerSettings'),
};
}
return this.environmentWriter;
}
public getConfigurationModify() {
if (!this.configModifier) {
this.configModifier = {
scheduler: this.proxify('getConfigurationModify:scheduler'),
slashCommands: {
_proxy: this.proxify('getConfigurationModify:slashCommands'),
modifySlashCommand(slashcommand: ISlashCommand) {
// Store the slashcommand instance to use when the Apps-Engine calls the slashcommand
AppObjectRegistry.set(`slashcommand:${slashcommand.command}`, slashcommand);
return this._proxy.modifySlashCommand(slashcommand);
},
disableSlashCommand(command: string) {
return this._proxy.disableSlashCommand(command);
},
enableSlashCommand(command: string) {
return this._proxy.enableSlashCommand(command);
},
},
serverSettings: this.proxify('getConfigurationModify:serverSettings'),
};
}
return this.configModifier;
}
public getConfigurationExtend() {
if (!this.configExtender) {
const senderFn = this.senderFn;
this.configExtender = {
ui: this.proxify('getConfigurationExtend:ui'),
http: this.httpExtend,
settings: this.proxify('getConfigurationExtend:settings'),
externalComponents: this.proxify('getConfigurationExtend:externalComponents'),
api: {
_proxy: this.proxify('getConfigurationExtend:api'),
async provideApi(api: IApi) {
const apiEndpoints = AppObjectRegistry.get<IApiEndpointMetadata[]>('apiEndpoints')!;
api.endpoints.forEach((endpoint) => {
endpoint._availableMethods = httpMethods.filter((method) => typeof endpoint[method] === 'function');
// We need to keep a reference to the endpoint around for us to call the executor later
AppObjectRegistry.set(`api:${endpoint.path}`, endpoint);
});
const result = await this._proxy.provideApi(api);
// Let's call the listApis method to cache the info from the endpoints
// Also, since this is a side-effect, we do it async so we can return to the caller
senderFn({ method: 'accessor:api:listApis' })
.then((response) => apiEndpoints.push(...(response.result as IApiEndpointMetadata[])))
.catch((err) => err.error);
return result;
},
},
scheduler: {
_proxy: this.proxify('getConfigurationExtend:scheduler'),
registerProcessors(processors: IProcessor[]) {
// Store the processor instance to use when the Apps-Engine calls the processor
processors.forEach((processor) => {
AppObjectRegistry.set(`scheduler:${processor.id}`, processor);
});
return this._proxy.registerProcessors(processors);
},
},
videoConfProviders: {
_proxy: this.proxify('getConfigurationExtend:videoConfProviders'),
provideVideoConfProvider(provider: IVideoConfProvider) {
// Store the videoConfProvider instance to use when the Apps-Engine calls the videoConfProvider
AppObjectRegistry.set(`videoConfProvider:${provider.name}`, provider);
return this._proxy.provideVideoConfProvider(provider);
},
},
slashCommands: {
_proxy: this.proxify('getConfigurationExtend:slashCommands'),
provideSlashCommand(slashcommand: ISlashCommand) {
// Store the slashcommand instance to use when the Apps-Engine calls the slashcommand
AppObjectRegistry.set(`slashcommand:${slashcommand.command}`, slashcommand);
return this._proxy.provideSlashCommand(slashcommand);
},
},
};
}
return this.configExtender;
}
public getDefaultAppAccessors() {
if (!this.defaultAppAccessors) {
this.defaultAppAccessors = {
environmentReader: this.getEnvironmentRead(),
environmentWriter: this.getEnvironmentWrite(),
reader: this.getReader(),
http: this.getHttp(),
providedApiEndpoints: AppObjectRegistry.get<IApiEndpointMetadata[]>('apiEndpoints') as IApiEndpointMetadata[],
};
}
return this.defaultAppAccessors;
}
public getReader() {
if (!this.reader) {
this.reader = {
getEnvironmentReader: () => ({
getSettings: () => this.proxify('getReader:getEnvironmentReader:getSettings'),
getServerSettings: () => this.proxify('getReader:getEnvironmentReader:getServerSettings'),
getEnvironmentVariables: () => this.proxify('getReader:getEnvironmentReader:getEnvironmentVariables'),
}),
getMessageReader: () => this.proxify('getReader:getMessageReader'),
getPersistenceReader: () => this.proxify('getReader:getPersistenceReader'),
getRoomReader: () => this.proxify('getReader:getRoomReader'),
getUserReader: () => this.proxify('getReader:getUserReader'),
getNotifier: () => this.getNotifier(),
getLivechatReader: () => this.proxify('getReader:getLivechatReader'),
getUploadReader: () => this.proxify('getReader:getUploadReader'),
getCloudWorkspaceReader: () => this.proxify('getReader:getCloudWorkspaceReader'),
getVideoConferenceReader: () => this.proxify('getReader:getVideoConferenceReader'),
getOAuthAppsReader: () => this.proxify('getReader:getOAuthAppsReader'),
getThreadReader: () => this.proxify('getReader:getThreadReader'),
getRoleReader: () => this.proxify('getReader:getRoleReader'),
getContactReader: () => this.proxify('getReader:getContactReader'),
};
}
return this.reader;
}
public getModifier() {
if (!this.modifier) {
this.modifier = {
getCreator: this.getCreator.bind(this),
getUpdater: this.getUpdater.bind(this),
getExtender: this.getExtender.bind(this),
getDeleter: () => this.proxify('getModifier:getDeleter'),
getNotifier: () => this.getNotifier(),
getUiController: () => this.proxify('getModifier:getUiController'),
getScheduler: () => this.proxify('getModifier:getScheduler'),
getOAuthAppsModifier: () => this.proxify('getModifier:getOAuthAppsModifier'),
getModerationModifier: () => this.proxify('getModifier:getModerationModifier'),
};
}
return this.modifier;
}
public getPersistence() {
if (!this.persistence) {
this.persistence = this.proxify('getPersistence');
}
return this.persistence;
}
public getHttp() {
return this.http;
}
private getCreator() {
if (!this.creator) {
this.creator = new ModifyCreator(this.senderFn);
}
return this.creator;
}
private getUpdater() {
if (!this.updater) {
this.updater = new ModifyUpdater(this.senderFn);
}
return this.updater;
}
private getExtender() {
if (!this.extender) {
this.extender = new ModifyExtender(this.senderFn);
}
return this.extender;
}
private getNotifier() {
return this.notifier;
}
}
export const AppAccessorsInstance = new AppAccessors(Messenger.sendRequest);