diff --git a/deno-runtime/lib/accessors/_test.ts b/deno-runtime/lib/accessors/_test.ts index 0960bb7a1..b650b9c90 100644 --- a/deno-runtime/lib/accessors/_test.ts +++ b/deno-runtime/lib/accessors/_test.ts @@ -1,22 +1,22 @@ import { beforeEach, describe, it } from 'https://deno.land/std@0.203.0/testing/bdd.ts'; import { assertEquals } from "https://deno.land/std@0.203.0/assert/assert_equals.ts"; -import { AppAccessors, getProxify } from "./mod.ts"; +import { AppAccessors } from "./mod.ts"; import { AppObjectRegistry } from "../../AppObjectRegistry.ts"; describe('AppAccessors', () => { let appAccessors: AppAccessors; - const proxify = getProxify((r) => Promise.resolve({ + const senderFn = (r: object) => Promise.resolve({ id: Math.random().toString(36).substring(2), jsonrpc: '2.0', result: r, serialize() { return JSON.stringify(this); } - })); + }); beforeEach(() => { - appAccessors = new AppAccessors(proxify); + appAccessors = new AppAccessors(senderFn); AppObjectRegistry.clear(); }); diff --git a/deno-runtime/lib/accessors/mod.ts b/deno-runtime/lib/accessors/mod.ts index 5c26aadcc..85a9a9d3d 100644 --- a/deno-runtime/lib/accessors/mod.ts +++ b/deno-runtime/lib/accessors/mod.ts @@ -14,23 +14,9 @@ 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 * as Messenger from '../messenger.ts'; -import { AppObjectRegistry } from "../../AppObjectRegistry.ts"; - -export const getProxify = (call: typeof Messenger.sendRequest) => - function proxify(namespace: string): T { - return new Proxy( - { __kind: namespace }, // debugging purposes - { - get: - (_target: unknown, prop: string) => - (...params: unknown[]) => - call({ - method: `accessor:${namespace}:${prop}`, - params, - }), - }, - ) as T; - }; +import { AppObjectRegistry } from '../../AppObjectRegistry.ts'; + +const httpMethods = ['get', 'post', 'put', 'delete', 'head', 'options', 'patch'] as const; export class AppAccessors { private defaultAppAccessors?: IAppAccessors; @@ -43,7 +29,23 @@ export class AppAccessors { private persistence?: IPersistence; private http?: IHttp; - constructor(private readonly proxify: (n: string) => T) {} + private proxify: (namespace: string) => T; + + constructor(senderFn: typeof Messenger.sendRequest) { + this.proxify = (namespace: string): T => + new Proxy( + { __kind: namespace }, + { + get: + (_target: unknown, prop: string) => + (...params: unknown[]) => + senderFn({ + method: `accessor:${namespace}:${prop}`, + params, + }), + }, + ) as T; + } public getEnvironmentRead(): IEnvironmentRead { if (!this.environmentRead) { @@ -72,7 +74,21 @@ export class AppAccessors { if (!this.configModifier) { this.configModifier = { scheduler: this.proxify('getConfigurationModify:scheduler'), - slashCommands: this.proxify('getConfigurationModify:slashCommands'), + 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'), }; } @@ -92,6 +108,8 @@ export class AppAccessors { provideApi(api: IApi) { api.endpoints.forEach((endpoint) => { AppObjectRegistry.set(`api:${endpoint.path}`, endpoint); + + endpoint._availableMethods = httpMethods.filter((method) => typeof endpoint[method] === 'function'); }); return this._proxy.provideApi(api); @@ -124,8 +142,8 @@ export class AppAccessors { AppObjectRegistry.set(`slashcommand:${slashcommand.command}`, slashcommand); return this._proxy.provideSlashCommand(slashcommand); - } - } + }, + }, }; } @@ -139,7 +157,7 @@ export class AppAccessors { environmentWriter: this.getEnvironmentWrite(), reader: this.getReader(), http: this.getHttp(), - providedApiEndpoints: this.proxify('providedApiEndpoints'), + providedApiEndpoints: this.proxify('api:listApis'), }; } @@ -207,4 +225,4 @@ export class AppAccessors { } } -export const AppAccessorsInstance = new AppAccessors(getProxify(Messenger.sendRequest.bind(Messenger))); +export const AppAccessorsInstance = new AppAccessors(Messenger.sendRequest); diff --git a/src/definition/api/IApiEndpoint.ts b/src/definition/api/IApiEndpoint.ts index a814ddc49..b369fc175 100644 --- a/src/definition/api/IApiEndpoint.ts +++ b/src/definition/api/IApiEndpoint.ts @@ -25,6 +25,14 @@ export interface IApiEndpoint { */ authRequired?: boolean; + /** + * The methods that are available for this endpoint. + * This property is provided by the Runtime and should not be set manually. + * + * Its values are used on the Apps-Engine to validate the request method. + */ + _availableMethods?: string[]; + /** * Called whenever the publically accessible url for this App is called, * if you handle the methods differently then split it out so your code doesn't get too big. diff --git a/src/server/managers/AppApi.ts b/src/server/managers/AppApi.ts index 0c756a8d6..cc371af83 100644 --- a/src/server/managers/AppApi.ts +++ b/src/server/managers/AppApi.ts @@ -7,8 +7,6 @@ import type { ProxiedApp } from '../ProxiedApp'; import type { AppLogStorage } from '../storage'; import type { AppAccessorManager } from './AppAccessorManager'; -const methods: Array = ['get', 'post', 'put', 'delete', 'head', 'options', 'patch']; - export class AppApi { public readonly computedPath: string; @@ -36,7 +34,7 @@ export class AppApi { this.computedPath = `${this.basePath}/${endpoint.path}`; - this.implementedMethods = methods.filter((m) => typeof (endpoint as any)[m] === 'function'); + this.implementedMethods = endpoint._availableMethods; } public async runExecutor(request: IApiRequest, logStorage: AppLogStorage, accessors: AppAccessorManager): Promise { @@ -45,7 +43,7 @@ export class AppApi { const { method } = request; // Ensure the api has the property before going on - if (typeof this.endpoint[method] !== 'function') { + if (!this.endpoint[method]) { return; } diff --git a/src/server/runtime/AppsEngineDenoRuntime.ts b/src/server/runtime/AppsEngineDenoRuntime.ts index 75cf7aef0..2a9fe2e0c 100644 --- a/src/server/runtime/AppsEngineDenoRuntime.ts +++ b/src/server/runtime/AppsEngineDenoRuntime.ts @@ -152,6 +152,12 @@ export class DenoRuntimeSubprocessController extends EventEmitter { const managerOrigin = accessorMethods.shift(); const tailMethodName = accessorMethods.pop(); + if (managerOrigin === 'api' && tailMethodName === 'listApis') { + const result = this.api.listApis(this.appId); + + return jsonrpc.success(id, result); + } + /** * At this point, the accessorMethods array will contain the path to the accessor from the origin (AppAccessorManager) * The accessor is the one that contains the actual method the app wants to call @@ -191,15 +197,14 @@ export class DenoRuntimeSubprocessController extends EventEmitter { ) => { const origin = accessorManager[managerOrigin](this.appId); - // These will need special treatment - if (managerOrigin === 'getConfigurationExtend' || managerOrigin === 'getConfigurationModify') { - return origin[accessorMethods[0] as keyof typeof origin]; - } - if (managerOrigin === 'getHttp' || managerOrigin === 'getPersistence') { return origin; } + if (managerOrigin === 'getConfigurationExtend' || managerOrigin === 'getConfigurationModify') { + return origin[accessorMethods[0] as keyof typeof origin]; + } + let accessor = origin; // Call all intermediary objects to "resolve" the accessor diff --git a/tests/test-data/utilities.ts b/tests/test-data/utilities.ts index 8e4f90987..cc6b1cc0f 100644 --- a/tests/test-data/utilities.ts +++ b/tests/test-data/utilities.ts @@ -244,6 +244,8 @@ export class TestData { endpoints: [ { path, + // The move to the Deno runtime now requires us to manually set what methods are available + _availableMethods: ['get'], get( request: IApiRequest, endpoint: IApiEndpointInfo,