Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/apps-engine/deno-runtime/deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"uuid": "npm:[email protected]"
},
"tasks": {
"test": "deno test --no-check --allow-read=../../../"
"test": "deno test --no-check --allow-read=../../../,/tmp --allow-write=/tmp"
},
"fmt": {
"lineWidth": 160,
Expand Down
12 changes: 7 additions & 5 deletions packages/apps-engine/deno-runtime/handlers/api-handler.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Defined, JsonRpcError } from 'jsonrpc-lite';
import type { IApiEndpoint } from '@rocket.chat/apps-engine/definition/api/IApiEndpoint.ts';
import { Defined, JsonRpcError } from 'jsonrpc-lite';

import { AppObjectRegistry } from '../AppObjectRegistry.ts';
import { Logger } from '../lib/logger.ts';
import { AppAccessorsInstance } from '../lib/accessors/mod.ts';
import { RequestContext } from '../lib/requestContext.ts';

export default async function apiHandler(call: string, params: unknown): Promise<JsonRpcError | Defined> {
export default async function apiHandler(request: RequestContext): Promise<JsonRpcError | Defined> {
const { method: call, params } = request;
const [, path, httpMethod] = call.split(':');

const endpoint = AppObjectRegistry.get<IApiEndpoint>(`api:${path}`);
Expand All @@ -21,14 +23,14 @@ export default async function apiHandler(call: string, params: unknown): Promise
return new JsonRpcError(`${path}'s ${httpMethod} not exists`, -32000);
}

const [request, endpointInfo] = params as Array<unknown>;
const [requestData, endpointInfo] = params as Array<unknown>;

logger?.debug(`${path}'s ${call} is being executed...`, request);
logger?.debug(`${path}'s ${call} is being executed...`, requestData);

try {
// deno-lint-ignore ban-types
const result = await (method as Function).apply(endpoint, [
request,
requestData,
endpointInfo,
AppAccessorsInstance.getReader(),
AppAccessorsInstance.getModifier(),
Expand Down
11 changes: 8 additions & 3 deletions packages/apps-engine/deno-runtime/handlers/app/construct.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Socket } from 'node:net';

import type { IParseAppPackageResult } from '@rocket.chat/apps-engine/server/compiler/IParseAppPackageResult.ts';

import { AppObjectRegistry } from '../../AppObjectRegistry.ts';
import { require } from '../../lib/require.ts';
import { sanitizeDeprecatedUsage } from '../../lib/sanitizeDeprecatedUsage.ts';
import { AppAccessorsInstance } from '../../lib/accessors/mod.ts';
import { Socket } from 'node:net';
import { RequestContext } from '../../lib/requestContext.ts';

const ALLOWED_NATIVE_MODULES = ['path', 'url', 'crypto', 'buffer', 'stream', 'net', 'http', 'https', 'zlib', 'util', 'punycode', 'os', 'querystring', 'fs'];
const ALLOWED_EXTERNAL_MODULES = ['uuid'];
Expand All @@ -13,7 +15,8 @@ function prepareEnvironment() {
// Deno does not behave equally to Node when it comes to piping content to a socket
// So we intervene here
const originalFinal = Socket.prototype._final;
Socket.prototype._final = function _final(cb) {
// deno-lint-ignore no-explicit-any
Socket.prototype._final = function _final(cb: any) {
// Deno closes the readable stream in the Socket earlier than Node
// The exact reason for that is yet unknown, so we'll need to simply delay the execution
// which allows data to be read in a response
Expand Down Expand Up @@ -71,7 +74,9 @@ function wrapAppCode(code: string): (require: (module: string) => unknown) => Pr
) as (require: (module: string) => unknown) => Promise<Record<string, unknown>>;
}

export default async function handleConstructApp(params: unknown): Promise<boolean> {
export default async function handleConstructApp(request: RequestContext): Promise<boolean> {
const { params } = request;

if (!Array.isArray(params)) {
throw new Error('Invalid params', { cause: 'invalid_param_type' });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import type { App } from '@rocket.chat/apps-engine/definition/App.ts';

import { AppObjectRegistry } from '../../AppObjectRegistry.ts';
import { AppAccessorsInstance } from '../../lib/accessors/mod.ts';
import { RequestContext } from '../../lib/requestContext.ts';

export default async function handleInitialize(): Promise<boolean> {
export default async function handleInitialize(_request: RequestContext): Promise<boolean> {
const app = AppObjectRegistry.get<App>('app');

if (typeof app?.initialize !== 'function') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import type { App } from '@rocket.chat/apps-engine/definition/App.ts';

import { AppObjectRegistry } from '../../AppObjectRegistry.ts';
import { AppAccessorsInstance } from '../../lib/accessors/mod.ts';
import { RequestContext } from '../../lib/requestContext.ts';

export default async function handleOnDisable(): Promise<boolean> {
export default async function handleOnDisable(_request: RequestContext): Promise<boolean> {
const app = AppObjectRegistry.get<App>('app');

if (typeof app?.onDisable !== 'function') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import type { App } from '@rocket.chat/apps-engine/definition/App.ts';

import { AppObjectRegistry } from '../../AppObjectRegistry.ts';
import { AppAccessorsInstance } from '../../lib/accessors/mod.ts';
import { RequestContext } from '../../lib/requestContext.ts';

export default function handleOnEnable(): Promise<boolean> {
export default function handleOnEnable(_request: RequestContext): Promise<boolean> {
const app = AppObjectRegistry.get<App>('app');

if (typeof app?.onEnable !== 'function') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import type { App } from '@rocket.chat/apps-engine/definition/App.ts';

import { AppObjectRegistry } from '../../AppObjectRegistry.ts';
import { AppAccessorsInstance } from '../../lib/accessors/mod.ts';
import { RequestContext } from '../../lib/requestContext.ts';

export default async function handleOnInstall(params: unknown): Promise<boolean> {
export default async function handleOnInstall(request: RequestContext): Promise<boolean> {
const { params } = request;
const app = AppObjectRegistry.get<App>('app');

if (typeof app?.onInstall !== 'function') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import type { App } from '@rocket.chat/apps-engine/definition/App.ts';

import { AppObjectRegistry } from '../../AppObjectRegistry.ts';
import { AppAccessorsInstance } from '../../lib/accessors/mod.ts';
import { RequestContext } from '../../lib/requestContext.ts';

export default function handleOnPreSettingUpdate(params: unknown): Promise<object> {
export default function handleOnPreSettingUpdate(request: RequestContext): Promise<object> {
const { params } = request;
const app = AppObjectRegistry.get<App>('app');

if (typeof app?.onPreSettingUpdate !== 'function') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import type { App } from '@rocket.chat/apps-engine/definition/App.ts';

import { AppObjectRegistry } from '../../AppObjectRegistry.ts';
import { AppAccessorsInstance } from '../../lib/accessors/mod.ts';
import { RequestContext } from '../../lib/requestContext.ts';

export default async function handleOnSettingUpdated(params: unknown): Promise<boolean> {
export default async function handleOnSettingUpdated(request: RequestContext): Promise<boolean> {
const { params } = request;
const app = AppObjectRegistry.get<App>('app');

if (typeof app?.onSettingUpdated !== 'function') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import type { App } from '@rocket.chat/apps-engine/definition/App.ts';

import { AppObjectRegistry } from '../../AppObjectRegistry.ts';
import { AppAccessorsInstance } from '../../lib/accessors/mod.ts';
import { RequestContext } from '../../lib/requestContext.ts';

export default async function handleOnUninstall(params: unknown): Promise<boolean> {
export default async function handleOnUninstall(request: RequestContext): Promise<boolean> {
const { params } = request;
const app = AppObjectRegistry.get<App>('app');

if (typeof app?.onUninstall !== 'function') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import type { App } from '@rocket.chat/apps-engine/definition/App.ts';

import { AppObjectRegistry } from '../../AppObjectRegistry.ts';
import { AppAccessorsInstance } from '../../lib/accessors/mod.ts';
import { RequestContext } from '../../lib/requestContext.ts';

export default async function handleOnUpdate(params: unknown): Promise<boolean> {
export default async function handleOnUpdate(request: RequestContext): Promise<boolean> {
const { params } = request;
const app = AppObjectRegistry.get<App>('app');

if (typeof app?.onUpdate !== 'function') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import type { AppStatus as _AppStatus } from '@rocket.chat/apps-engine/definitio

import { AppObjectRegistry } from '../../AppObjectRegistry.ts';
import { require } from '../../lib/require.ts';
import { RequestContext } from '../../lib/requestContext.ts';

const { AppStatus } = require('@rocket.chat/apps-engine/definition/AppStatus.js') as {
AppStatus: typeof _AppStatus;
};

export default async function handleSetStatus(params: unknown): Promise<null> {
export default async function handleSetStatus(request: RequestContext): Promise<null> {
const { params } = request;

if (!Array.isArray(params) || !Object.values(AppStatus).includes(params[0])) {
throw new Error('Invalid params', { cause: 'invalid_param_type' });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Defined, JsonRpcError } from 'jsonrpc-lite';
import { AppObjectRegistry } from '../../AppObjectRegistry.ts';
import { assertAppAvailable, assertHandlerFunction, isRecord } from '../lib/assertions.ts';
import { AppAccessorsInstance } from '../../lib/accessors/mod.ts';
import { RequestContext } from '../../lib/requestContext.ts';

export const uploadEvents = ['executePreFileUpload'] as const;

Expand All @@ -25,13 +26,16 @@ function assertString(v: unknown): asserts v is string {
throw JsonRpcError.invalidParams({ err: `Invalid 'path' parameter. Expected string, got`, value: v });
}

export default async function handleUploadEvents(method: typeof uploadEvents[number], params: unknown): Promise<Defined | JsonRpcError> {
const [{ file, path }] = params as [{ file?: IUpload, path?: string }];

const app = AppObjectRegistry.get<App>('app');
const handlerFunction = app?.[method as keyof App] as unknown;
export default async function handleUploadEvents(request: RequestContext): Promise<Defined | JsonRpcError> {
const { method: rawMethod, params } = request as { method: `app:${typeof uploadEvents[number]}`; params: [{ file?: IUploadDetails, path?: string }]};
const [, method] = rawMethod.split(':') as ['app', typeof uploadEvents[number]];

try {
const [{ file, path }] = params;

const app = AppObjectRegistry.get<App>('app');
const handlerFunction = app?.[method as keyof App] as unknown;

assertAppAvailable(app);
assertHandlerFunction(handlerFunction);
assertIsUpload(file);
Expand Down
36 changes: 19 additions & 17 deletions packages/apps-engine/deno-runtime/handlers/app/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ import handleOnDisable from './handleOnDisable.ts';
import handleOnUninstall from './handleOnUninstall.ts';
import handleOnPreSettingUpdate from './handleOnPreSettingUpdate.ts';
import handleOnSettingUpdated from './handleOnSettingUpdated.ts';
import handleListener from '../listener/handler.ts';
import handleUIKitInteraction, { uikitInteractions } from '../uikit/handler.ts';
import { AppObjectRegistry } from '../../AppObjectRegistry.ts';
import handleOnUpdate from './handleOnUpdate.ts';
import handleUploadEvents, { uploadEvents } from './handleUploadEvents.ts';
import handleListener from '../listener/handler.ts';
import handleUIKitInteraction, { uikitInteractions } from '../uikit/handler.ts';
import { isOneOf } from '../lib/assertions.ts';
import { AppObjectRegistry } from '../../AppObjectRegistry.ts';
import { RequestContext } from '../../lib/requestContext.ts';

export default async function handleApp(method: string, params: unknown): Promise<Defined | JsonRpcError> {
export default async function handleApp(request: RequestContext): Promise<Defined | JsonRpcError> {
const { method } = request;
const [, appMethod] = method.split(':');

try {
Expand Down Expand Up @@ -52,49 +54,49 @@ export default async function handleApp(method: string, params: unknown): Promis
};

if (app && isOneOf(appMethod, uploadEvents)) {
return handleUploadEvents(appMethod, params).then(formatResult);
return handleUploadEvents(request).then(formatResult);
}

if (app && isOneOf(appMethod, uikitInteractions)) {
return handleUIKitInteraction(appMethod, params).then(formatResult);
return handleUIKitInteraction(request).then(formatResult);
}

if (app && (appMethod.startsWith('check') || appMethod.startsWith('execute'))) {
return handleListener(appMethod, params).then(formatResult);
return handleListener(request).then(formatResult);
}

let result: Defined | JsonRpcError;

switch (appMethod) {
case 'construct':
result = await handleConstructApp(params);
result = await handleConstructApp(request);
break;
case 'initialize':
result = await handleInitialize();
result = await handleInitialize(request);
break;
case 'setStatus':
result = await handleSetStatus(params);
result = await handleSetStatus(request);
break;
case 'onEnable':
result = await handleOnEnable();
result = await handleOnEnable(request);
break;
case 'onDisable':
result = await handleOnDisable();
result = await handleOnDisable(request);
break;
case 'onInstall':
result = await handleOnInstall(params);
result = await handleOnInstall(request);
break;
case 'onUninstall':
result = await handleOnUninstall(params);
result = await handleOnUninstall(request);
break;
case 'onPreSettingUpdate':
result = await handleOnPreSettingUpdate(params);
result = await handleOnPreSettingUpdate(request);
break;
case 'onSettingUpdated':
result = await handleOnSettingUpdated(params);
result = await handleOnSettingUpdated(request);
break;
case 'onUpdate':
result = await handleOnUpdate(params);
result = await handleOnUpdate(request);
break;
default:
throw new JsonRpcError('Method not found', -32601);
Expand Down
14 changes: 12 additions & 2 deletions packages/apps-engine/deno-runtime/handlers/lib/assertions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import type { App } from '@rocket.chat/apps-engine/definition/App.ts';
import { JsonRpcError } from 'jsonrpc-lite';

/**
* Known failures that can happen in the runtime.
*
* DRT = Deno RunTime
*/
export const Errors = {
DRT_APP_NOT_AVAILABLE: 'DRT_APP_NOT_AVAILABLE',
DRT_EVENT_HANDLER_FUNCTION_MISSING: 'DRT_EVENT_HANDLER_FUNCTION_MISSING',
}

export function isRecord(v: unknown): v is Record<string, unknown> {
if (!v || typeof v !== 'object') {
return false;
Expand All @@ -22,12 +32,12 @@ export function isOneOf<T>(value: unknown, array: readonly T[]): value is T {
export function assertAppAvailable(v: unknown): asserts v is App {
if (v && typeof (v as App)['extendConfiguration'] === 'function') return;

throw JsonRpcError.internalError({ err: 'App object not available' });
throw JsonRpcError.internalError({ err: 'App object not available', code: Errors.DRT_APP_NOT_AVAILABLE });
}

// deno-lint-ignore ban-types -- Function is the best we can do at this time
export function assertHandlerFunction(v: unknown): asserts v is Function {
if (v instanceof Function) return;

throw JsonRpcError.internalError({ err: `Expected handler function, got ${v}` });
throw JsonRpcError.internalError({ err: `Expected handler function, got ${v}`, code: Errors.DRT_EVENT_HANDLER_FUNCTION_MISSING });
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Defined, JsonRpcError } from 'jsonrpc-lite';
import type { App } from '@rocket.chat/apps-engine/definition/App.ts';
import type { IMessage } from '@rocket.chat/apps-engine/definition/messages/IMessage.ts';
import type { IRoom } from '@rocket.chat/apps-engine/definition/rooms/IRoom.ts';
import type { AppsEngineException as _AppsEngineException } from '@rocket.chat/apps-engine/definition/exceptions/AppsEngineException.ts';
import { Defined, JsonRpcError } from 'jsonrpc-lite';

import { AppObjectRegistry } from '../../AppObjectRegistry.ts';
import { MessageExtender } from '../../lib/accessors/extenders/MessageExtender.ts';
Expand All @@ -13,12 +13,15 @@ import { AppAccessors, AppAccessorsInstance } from '../../lib/accessors/mod.ts';
import { require } from '../../lib/require.ts';
import createRoom from '../../lib/roomFactory.ts';
import { Room } from '../../lib/room.ts';
import { RequestContext } from '../../lib/requestContext.ts';

const { AppsEngineException } = require('@rocket.chat/apps-engine/definition/exceptions/AppsEngineException.js') as {
AppsEngineException: typeof _AppsEngineException;
};

export default async function handleListener(evtInterface: string, params: unknown): Promise<Defined | JsonRpcError> {
export default async function handleListener(request: RequestContext): Promise<Defined | JsonRpcError> {
const { method, params } = request;
const [, evtInterface] = method.split(':');
const app = AppObjectRegistry.get<App>('app');

const eventExecutor = app?.[evtInterface as keyof App];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { JsonRpcError, Defined } from 'jsonrpc-lite';
import type { IOutboundMessageProviders } from '@rocket.chat/apps-engine/definition/outboundCommunication/IOutboundCommsProvider.ts';
import { JsonRpcError, Defined } from 'jsonrpc-lite';

import { AppObjectRegistry } from '../AppObjectRegistry.ts';
import { AppAccessorsInstance } from '../lib/accessors/mod.ts';
import { Logger } from '../lib/logger.ts';
import { RequestContext } from '../lib/requestContext.ts';

export default async function outboundMessageHandler(call: string, params: unknown): Promise<JsonRpcError | Defined> {
export default async function outboundMessageHandler(request: RequestContext): Promise<JsonRpcError | Defined> {
const { method: call, params } = request;
const [, providerName, methodName] = call.split(':');

const provider = AppObjectRegistry.get<IOutboundMessageProviders>(`outboundCommunication:${providerName}`);
if (!provider) {
return new JsonRpcError('error-invalid-provider', -32000);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import type { IProcessor } from '@rocket.chat/apps-engine/definition/scheduler/I

import { AppObjectRegistry } from '../AppObjectRegistry.ts';
import { AppAccessorsInstance } from '../lib/accessors/mod.ts';
import { RequestContext } from '../lib/requestContext.ts';

export default async function handleScheduler(method: string, params: unknown): Promise<Defined | JsonRpcError> {
export default async function handleScheduler(request: RequestContext): Promise<Defined | JsonRpcError> {
const { method, params } = request;
const [, processorId] = method.split(':');
if (!Array.isArray(params)) {
return JsonRpcError.invalidParams({ message: 'Invalid params' });
Expand Down
Loading
Loading