diff --git a/deno-runtime/handlers/tests/videoconference-handler.test.ts b/deno-runtime/handlers/tests/videoconference-handler.test.ts new file mode 100644 index 000000000..de440ef90 --- /dev/null +++ b/deno-runtime/handlers/tests/videoconference-handler.test.ts @@ -0,0 +1,117 @@ +// deno-lint-ignore-file no-explicit-any +import { assertEquals, assertObjectMatch } from 'https://deno.land/std@0.203.0/assert/mod.ts'; +import { beforeEach, describe, it } from 'https://deno.land/std@0.203.0/testing/bdd.ts'; +import { spy } from "https://deno.land/std@0.203.0/testing/mock.ts"; + +import { AppObjectRegistry } from '../../AppObjectRegistry.ts'; +import videoconfHandler from '../videoconference-handler.ts'; +import { assertInstanceOf } from "https://deno.land/std@0.203.0/assert/assert_instance_of.ts"; +import { JsonRpcError } from "jsonrpc-lite"; + +describe('handlers > videoconference', () => { + // deno-lint-ignore no-unused-vars + const mockMethodWithoutParam = (read: any, modify: any, http: any, persis: any): Promise => Promise.resolve('ok none'); + // deno-lint-ignore no-unused-vars + const mockMethodWithOneParam = (call: any, read: any, modify: any, http: any, persis: any): Promise => Promise.resolve('ok one'); + // deno-lint-ignore no-unused-vars + const mockMethodWithTwoParam = (call: any, user: any, read: any, modify: any, http: any, persis: any): Promise => Promise.resolve('ok two'); + // deno-lint-ignore no-unused-vars + const mockMethodWithThreeParam = (call: any, user: any, options: any, read: any, modify: any, http: any, persis: any): Promise => Promise.resolve('ok three'); + const mockProvider = { + empty: mockMethodWithoutParam, + one: mockMethodWithOneParam, + two: mockMethodWithTwoParam, + three: mockMethodWithThreeParam, + notAFunction: true, + error: () => { throw new Error('Method execution error example') } + } + + beforeEach(() => { + AppObjectRegistry.clear(); + AppObjectRegistry.set('videoConfProvider:test-provider', mockProvider); + }); + + it('correctly handles execution of a videoconf method without additional params', async () => { + const _spy = spy(mockProvider, 'empty'); + + const result = await videoconfHandler('videoconference:test-provider:empty', []); + + assertEquals(result, 'ok none') + assertEquals(_spy.calls[0].args.length, 4); + + _spy.restore(); + }); + + it('correctly handles execution of a videoconf method with one param', async () => { + const _spy = spy(mockProvider, 'one'); + + const result = await videoconfHandler('videoconference:test-provider:one', ['call']); + + assertEquals(result, 'ok one') + assertEquals(_spy.calls[0].args.length, 5); + assertEquals(_spy.calls[0].args[0], 'call'); + + _spy.restore(); + }); + + it('correctly handles execution of a videoconf method with two params', async () => { + const _spy = spy(mockProvider, 'two'); + + const result = await videoconfHandler('videoconference:test-provider:two', ['call', 'user']); + + assertEquals(result, 'ok two') + assertEquals(_spy.calls[0].args.length, 6); + assertEquals(_spy.calls[0].args[0], 'call'); + assertEquals(_spy.calls[0].args[1], 'user'); + + _spy.restore(); + }); + + it('correctly handles execution of a videoconf method with three params', async () => { + const _spy = spy(mockProvider, 'three'); + + const result = await videoconfHandler('videoconference:test-provider:three', ['call', 'user', 'options']); + + assertEquals(result, 'ok three') + assertEquals(_spy.calls[0].args.length, 7); + assertEquals(_spy.calls[0].args[0], 'call'); + assertEquals(_spy.calls[0].args[1], 'user'); + assertEquals(_spy.calls[0].args[2], 'options'); + + _spy.restore(); + }); + + it('correctly handles an error on execution of a videoconf method', async () => { + const result = await videoconfHandler('videoconference:test-provider:error', []); + + assertInstanceOf(result, JsonRpcError) + assertObjectMatch(result, { + message: 'Method execution error example', + code: -32000 + }) + }) + + it('correctly handles an error when provider is not found', async () => { + const providerName = 'error-provider' + const result = await videoconfHandler(`videoconference:${providerName}:method`, []); + + assertInstanceOf(result, JsonRpcError) + assertObjectMatch(result, { + message: `Provider ${providerName} not found`, + code: -32000 + }) + }) + + it('correctly handles an error if method is not a function of provider', async () => { + const methodName = 'notAFunction' + const providerName = 'test-provider' + const result = await videoconfHandler(`videoconference:${providerName}:${methodName}`, []); + + assertInstanceOf(result, JsonRpcError) + assertObjectMatch(result, { + message: `Method ${methodName} not found on provider ${providerName}`, + code: -32000 + }) + }) + +}); diff --git a/deno-runtime/handlers/videoconference-handler.ts b/deno-runtime/handlers/videoconference-handler.ts new file mode 100644 index 000000000..b347e8eb8 --- /dev/null +++ b/deno-runtime/handlers/videoconference-handler.ts @@ -0,0 +1,50 @@ +import { Defined, JsonRpcError } from "jsonrpc-lite"; +import { AppObjectRegistry } from "../AppObjectRegistry.ts"; +import { IVideoConfProvider } from "@rocket.chat/apps-engine/definition/videoConfProviders/IVideoConfProvider.ts"; +import { AppAccessorsInstance } from "../lib/accessors/mod.ts"; +import { Logger } from "../lib/logger.ts"; + +export default async function videoConferenceHandler(call: string, params: unknown): Promise { + const [, providerName, methodName] = call.split(':'); + + const provider = AppObjectRegistry.get(`videoConfProvider:${providerName}`); + const logger = AppObjectRegistry.get('logger'); + + if (!provider) { + return new JsonRpcError(`Provider ${providerName} not found`, -32000); + } + + const method = provider[methodName as keyof IVideoConfProvider]; + + if (typeof method !== 'function') { + return new JsonRpcError(`Method ${methodName} not found on provider ${providerName}`, -32000); + } + + const [videoconf, user, options] = params as Array; + + logger?.debug(`Executing ${methodName} on video conference provider...`); + + const args = [ + ...(videoconf ? [videoconf] : []), + ...(user ? [user] : []), + ...(options ? [options] : []), + ]; + + try { + // deno-lint-ignore ban-types + const result = await(method as Function).apply(provider, [ + ...args, + AppAccessorsInstance.getReader(), + AppAccessorsInstance.getModifier(), + AppAccessorsInstance.getHttp(), + AppAccessorsInstance.getPersistence() + ]); + + logger?.debug(`Video Conference Provider's ${methodName} was successfully executed.`); + + return result; + } catch (e) { + logger?.debug(`Video Conference Provider's ${methodName} was unsuccessful.`); + return new JsonRpcError(e.message, -32000); + } +} diff --git a/deno-runtime/main.ts b/deno-runtime/main.ts index 9735a7c1b..c0affec9d 100644 --- a/deno-runtime/main.ts +++ b/deno-runtime/main.ts @@ -16,11 +16,24 @@ import { AppObjectRegistry } from './AppObjectRegistry.ts'; import { Logger } from './lib/logger.ts'; import slashcommandHandler from './handlers/slashcommand-handler.ts'; +import videoConferenceHandler from './handlers/videoconference-handler.ts'; import handleApp from './handlers/app/handler.ts'; AppObjectRegistry.set('MESSAGE_SEPARATOR', Deno.args.at(-1)); +type Handlers = { + 'app': typeof handleApp, + 'slashcommand': typeof slashcommandHandler + 'videoconference': typeof videoConferenceHandler +} + async function requestRouter({ type, payload }: Messenger.JsonRpcRequest): Promise { + const methodHandlers: Handlers = { + 'app': handleApp, + 'slashcommand': slashcommandHandler, + 'videoconference': videoConferenceHandler + } + // We're not handling notifications at the moment if (type === 'notification') { return Messenger.sendInvalidRequestError(); @@ -38,34 +51,23 @@ async function requestRouter({ type, payload }: Messenger.JsonRpcRequest): Promi (app as unknown as Record).logger = logger; } - switch (true) { - case method.startsWith('app:'): { - const result = await handleApp(method, params); + const [methodPrefix] = method.split(':') as [keyof Handlers]; + const handler = methodHandlers[methodPrefix] - if (result instanceof JsonRpcError) { - return Messenger.errorResponse({ id, error: result }); - } - - Messenger.successResponse({ id, result }); - break; - } - case method.startsWith('slashcommand:'): { - const result = await slashcommandHandler(method, params); + if (!handler) { + return Messenger.errorResponse({ + error: { message: 'Method not found', code: -32601 }, + id, + }); + } - if (result instanceof JsonRpcError) { - return Messenger.errorResponse({ id, error: result }); - } + const result = await handler(method, params); - return Messenger.successResponse({ id, result }); - } - default: { - Messenger.errorResponse({ - error: { message: 'Method not found', code: -32601 }, - id, - }); - break; - } + if (result instanceof JsonRpcError) { + return Messenger.errorResponse({ id, error: result }); } + + return Messenger.successResponse({ id, result }); } function handleResponse(response: Messenger.JsonRpcResponse): void { diff --git a/src/server/managers/AppVideoConfProvider.ts b/src/server/managers/AppVideoConfProvider.ts index c2f0143f9..8f9398a32 100644 --- a/src/server/managers/AppVideoConfProvider.ts +++ b/src/server/managers/AppVideoConfProvider.ts @@ -3,7 +3,6 @@ import type { IBlock } from '../../definition/uikit'; import type { VideoConference } from '../../definition/videoConferences'; import type { IVideoConferenceUser } from '../../definition/videoConferences/IVideoConferenceUser'; import type { IVideoConferenceOptions, IVideoConfProvider, VideoConfData, VideoConfDataExtended } from '../../definition/videoConfProviders'; -import { AppConsole } from '../logging'; import type { ProxiedApp } from '../ProxiedApp'; import type { AppLogStorage } from '../storage'; import type { AppAccessorManager } from './AppAccessorManager'; @@ -83,46 +82,22 @@ export class AppVideoConfProvider { | AppMethod._VIDEOCONF_CHANGED | AppMethod._VIDEOCONF_GET_INFO | AppMethod._VIDEOCONF_USER_JOINED, - logStorage: AppLogStorage, - accessors: AppAccessorManager, + _logStorage: AppLogStorage, + _accessors: AppAccessorManager, runContextArgs: Array, ): Promise | undefined> { - // Ensure the provider has the property before going on - if (typeof this.provider[method] !== 'function') { - return; - } - - const runContext = { - provider: this.provider, - args: [ - ...runContextArgs, - accessors.getReader(this.app.getID()), - accessors.getModifier(this.app.getID()), - accessors.getHttp(this.app.getID()), - accessors.getPersistence(this.app.getID()), - ], - }; - - const logger = this.app.setupLogger(method); - logger.debug(`Executing ${method} on video conference provider...`); + const provider = this.provider.name; - let result: string | undefined; try { - const runCode = `module.exports = provider.${method}.apply(provider, args)`; - result = await this.app.getRuntime().runInSandbox(runCode, runContext); - logger.debug(`Video Conference Provider's ${method} was successfully executed.`); - } catch (e) { - logger.error(e); - logger.debug(`Video Conference Provider's ${method} was unsuccessful.`); - } + const result = await this.app.getDenoRuntime().sendRequest({ + method: `videoconference.${provider}.${method}`, + params: [runContextArgs], + }); - try { - await logStorage.storeEntries(AppConsole.toStorageEntry(this.app.getID(), logger)); + return result as string; } catch (e) { - // Don't care, at the moment. - // TODO: Evaluate to determine if we do care + // @TODO add error handling + console.log(e); } - - return result; } }