Skip to content
This repository has been archived by the owner on Sep 30, 2024. It is now read-only.

Adapted config accessors #675

Merged
merged 3 commits into from
Nov 28, 2023
Merged
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
8 changes: 4 additions & 4 deletions deno-runtime/lib/accessors/_test.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { beforeEach, describe, it } from 'https://deno.land/[email protected]/testing/bdd.ts';
import { assertEquals } from "https://deno.land/[email protected]/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();
});

Expand Down
64 changes: 41 additions & 23 deletions deno-runtime/lib/accessors/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(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;
Expand All @@ -43,7 +29,23 @@ export class AppAccessors {
private persistence?: IPersistence;
private http?: IHttp;

constructor(private readonly proxify: <T>(n: string) => T) {}
private proxify: <T>(namespace: string) => T;

constructor(senderFn: typeof Messenger.sendRequest) {
this.proxify = <T>(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) {
Expand Down Expand Up @@ -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'),
};
}
Expand All @@ -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);
Expand Down Expand Up @@ -124,8 +142,8 @@ export class AppAccessors {
AppObjectRegistry.set(`slashcommand:${slashcommand.command}`, slashcommand);

return this._proxy.provideSlashCommand(slashcommand);
}
}
},
},
};
}

Expand All @@ -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'),
};
}

Expand Down Expand Up @@ -207,4 +225,4 @@ export class AppAccessors {
}
}

export const AppAccessorsInstance = new AppAccessors(getProxify(Messenger.sendRequest.bind(Messenger)));
export const AppAccessorsInstance = new AppAccessors(Messenger.sendRequest);
8 changes: 8 additions & 0 deletions src/definition/api/IApiEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 2 additions & 4 deletions src/server/managers/AppApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import type { ProxiedApp } from '../ProxiedApp';
import type { AppLogStorage } from '../storage';
import type { AppAccessorManager } from './AppAccessorManager';

const methods: Array<string> = ['get', 'post', 'put', 'delete', 'head', 'options', 'patch'];

export class AppApi {
public readonly computedPath: string;

Expand Down Expand Up @@ -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<IApiResponse> {
Expand All @@ -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;
}

Expand Down
15 changes: 10 additions & 5 deletions src/server/runtime/AppsEngineDenoRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions tests/test-data/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading