From c27412b0bcce47745d5b13938353073a5fddcd8f Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo Date: Thu, 9 Jun 2022 09:44:00 -0300 Subject: [PATCH] Regression: fix apps path (#25809) --- apps/meteor/app/api/server/v1/misc.ts | 28 ++-- .../app/apps/client/@types/IOrchestrator.ts | 19 +-- apps/meteor/app/apps/client/orchestrator.ts | 157 +++++++++--------- apps/meteor/client/lib/meteorCallWrapper.ts | 8 +- apps/meteor/client/lib/userData.ts | 27 ++- .../client/providers/ServerProvider.tsx | 8 +- .../views/admin/apps/AppDetailsPage.tsx | 4 +- .../views/admin/apps/hooks/useCategories.ts | 3 +- packages/core-typings/src/IUser.ts | 1 + packages/rest-typings/src/apps/index.ts | 63 ++++++- packages/rest-typings/src/v1/me.ts | 71 ++++---- packages/rest-typings/src/v1/misc.ts | 28 +--- 12 files changed, 251 insertions(+), 166 deletions(-) diff --git a/apps/meteor/app/api/server/v1/misc.ts b/apps/meteor/app/api/server/v1/misc.ts index 9a7b7135a56..841cca8e885 100644 --- a/apps/meteor/app/api/server/v1/misc.ts +++ b/apps/meteor/app/api/server/v1/misc.ts @@ -14,6 +14,7 @@ import { isMethodCallAnonProps, isMeteorCall, } from '@rocket.chat/rest-typings'; +import { IUser } from '@rocket.chat/core-typings'; import { hasPermission } from '../../../authorization/server'; import { Users } from '../../../models/server'; @@ -166,17 +167,24 @@ API.v1.addRoute( 'me', { authRequired: true }, { - get() { + async get() { const fields = getDefaultUserFields(); - const user = Users.findOneById(this.userId, { fields }); - - // The password hash shouldn't be leaked but the client may need to know if it exists. - if (user?.services?.password?.bcrypt) { - user.services.password.exists = true; - delete user.services.password.bcrypt; - } - - return API.v1.success(this.getUserInfo(user)); + const { services, ...user } = Users.findOneById(this.userId, { fields }) as IUser; + + return API.v1.success( + this.getUserInfo({ + ...user, + ...(services && { + services: { + ...services, + password: { + // The password hash shouldn't be leaked but the client may need to know if it exists. + exists: Boolean(services?.password?.bcrypt), + } as any, + }, + }), + }), + ); }, }, ); diff --git a/apps/meteor/app/apps/client/@types/IOrchestrator.ts b/apps/meteor/app/apps/client/@types/IOrchestrator.ts index f178cd03960..55400407a94 100644 --- a/apps/meteor/app/apps/client/@types/IOrchestrator.ts +++ b/apps/meteor/app/apps/client/@types/IOrchestrator.ts @@ -1,4 +1,3 @@ -import { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata/IAppInfo'; import { ISetting } from '@rocket.chat/apps-engine/definition/settings/ISetting'; export interface IDetailedDescription { @@ -148,7 +147,6 @@ export interface IAppLanguage { export interface IAppExternalURL { url: string; - success: boolean; } export interface ICategory { @@ -159,10 +157,10 @@ export interface ICategory { title: string; } -export interface IDeletedInstalledApp { - app: IAppInfo; - success: boolean; -} +// export interface IDeletedInstalledApp { +// app: IAppInfo; +// success: boolean; +// } export interface IAppSynced { app: IAppFromMarketplace; @@ -193,12 +191,3 @@ export interface ISettingsReturn { settings: ISettings; success: boolean; } - -export interface ISettingsPayload { - settings: ISetting[]; -} - -export interface ISettingsSetReturn { - updated: ISettings; - success: boolean; -} diff --git a/apps/meteor/app/apps/client/orchestrator.ts b/apps/meteor/app/apps/client/orchestrator.ts index 0062d620699..dc37aef84e7 100644 --- a/apps/meteor/app/apps/client/orchestrator.ts +++ b/apps/meteor/app/apps/client/orchestrator.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-var-requires */ +import type { ISetting } from '@rocket.chat/apps-engine/definition/settings'; import { AppClientManager } from '@rocket.chat/apps-engine/client/AppClientManager'; import { IApiEndpointMetadata } from '@rocket.chat/apps-engine/definition/api'; import { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; @@ -6,6 +7,7 @@ import { IPermission } from '@rocket.chat/apps-engine/definition/permissions/IPe import { IAppStorageItem } from '@rocket.chat/apps-engine/server/storage/IAppStorageItem'; import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; +import { AppScreenshot, Serialized } from '@rocket.chat/core-typings'; import { App } from '../../../client/views/admin/apps/types'; import { dispatchToastMessage } from '../../../client/lib/toast'; @@ -15,27 +17,23 @@ import { createDeferredValue } from '../lib/misc/DeferredValue'; import { IPricingPlan, EAppPurchaseType, - IAppFromMarketplace, + // IAppFromMarketplace, IAppLanguage, IAppExternalURL, ICategory, - IDeletedInstalledApp, - IAppSynced, - IAppScreenshots, + // IAppSynced, + // IAppScreenshots, + // IScreenshot, IAuthor, IDetailedChangelog, IDetailedDescription, ISubscriptionInfo, - ISettingsReturn, - ISettingsPayload, - ISettingsSetReturn, } from './@types/IOrchestrator'; import { AppWebsocketReceiver } from './communication'; import { handleI18nResources } from './i18n'; import { RealAppsEngineUIHost } from './RealAppsEngineUIHost'; - -const { APIClient } = require('../../utils'); -const { hasAtLeastOnePermission } = require('../../authorization'); +import { APIClient } from '../../utils/client'; +import { hasAtLeastOnePermission } from '../../authorization/client'; export interface IAppsFromMarketplace { price: number; @@ -123,8 +121,9 @@ class AppClientOrchestrator { } } - public screenshots(appId: string): IAppScreenshots { - return APIClient.get(`/v1/apps/${appId}/screenshots`); + public async screenshots(appId: string): Promise { + const { screenshots } = await APIClient.get(`/apps/${appId}/screenshots`); + return screenshots; } public isEnabled(): Promise | undefined { @@ -132,79 +131,87 @@ class AppClientOrchestrator { } public async getApps(): Promise { - const { apps } = await APIClient.get('/v1/apps'); - return apps; + const result = await APIClient.get('/apps'); + if ('apps' in result) { + return result.apps; + } + throw new Error('Apps not found'); } - public async getAppsFromMarketplace(): Promise { - const appsOverviews: IAppFromMarketplace[] = await APIClient.get('/v1/apps', { marketplace: 'true' }); - return appsOverviews.map((app: IAppFromMarketplace) => { - const { latest, price, pricingPlans, purchaseType, isEnterpriseOnly, modifiedAt } = app; - return { - ...latest, - price, - pricingPlans, - purchaseType, - isEnterpriseOnly, - modifiedAt, - }; - }); + public async getAppsFromMarketplace(): Promise { + const result = await APIClient.get('/apps', { marketplace: 'true' }); + + if ('apps' in result) { + const { apps: appsOverviews } = result; + return appsOverviews.map((app) => { + const { latest, price, pricingPlans, purchaseType, isEnterpriseOnly, modifiedAt } = app; + return { + ...latest, + price, + pricingPlans, + purchaseType, + isEnterpriseOnly, + modifiedAt, + }; + }); + } + throw new Error('Apps not found'); } public async getAppsOnBundle(bundleId: string): Promise { - const { apps } = await APIClient.get(`/v1/apps/bundles/${bundleId}/apps`); + const { apps } = await APIClient.get(`/apps/bundles/${bundleId}/apps`); return apps; } public async getAppsLanguages(): Promise { - const { apps } = await APIClient.get('/v1/apps/languages'); + const { apps } = await APIClient.get('/apps/languages'); return apps; } public async getApp(appId: string): Promise { - const { app } = await APIClient.get(`/v1/apps/${appId}`); + const { app } = await APIClient.get(`/apps/${appId}` as any); return app; } public async getAppFromMarketplace(appId: string, version: string): Promise { - const { app } = await APIClient.get(`/v1/apps/${appId}`, { - marketplace: 'true', - version, - }); - return app; + const result = await APIClient.get( + `/apps/${appId}` as any, + { + marketplace: 'true', + version, + } as any, + ); + return result; } public async getLatestAppFromMarketplace(appId: string, version: string): Promise { - const { app } = await APIClient.get(`/v1/apps/${appId}`, { - marketplace: 'true', - update: 'true', - appVersion: version, - }); + const { app } = await APIClient.get( + `/apps/${appId}` as any, + { + marketplace: 'true', + update: 'true', + appVersion: version, + } as any, + ); return app; } - public async getAppSettings(appId: string): Promise { - const { settings } = await APIClient.get(`/v1/apps/${appId}/settings`); - return settings; - } - - public async setAppSettings(appId: string, settings: ISettingsPayload): Promise { - const { updated } = await APIClient.post(`/v1/apps/${appId}/settings`, undefined, { settings }); - return updated; + public async setAppSettings(appId: string, settings: ISetting[]): Promise { + await APIClient.post(`/apps/${appId}/settings`, { settings }); } public async getAppApis(appId: string): Promise { - const { apis } = await APIClient.get(`/v1/apps/${appId}/apis`); + const { apis } = await APIClient.get(`/apps/${appId}/apis`); return apis; } public async getAppLanguages(appId: string): Promise { - const { languages } = await APIClient.get(`/v1/apps/${appId}/languages`); + const { languages } = await APIClient.get(`/apps/${appId}/languages`); return languages; } - public async installApp(appId: string, version: string, permissionsGranted: IPermission[]): Promise { - const { app } = await APIClient.post('/v1/apps/', { + public async installApp(appId: string, version: string, permissionsGranted: IPermission[]): Promise { + const { app } = await APIClient.post('/apps', { appId, marketplace: true, version, @@ -214,48 +221,48 @@ class AppClientOrchestrator { } public async updateApp(appId: string, version: string, permissionsGranted: IPermission[]): Promise { - const { app } = await APIClient.post(`/v1/apps/${appId}`, { + const result = (await (APIClient.post as any)(`/apps/${appId}` as any, { appId, marketplace: true, version, permissionsGranted, - }); - return app; - } + })) as any; - public uninstallApp(appId: string): IDeletedInstalledApp { - return APIClient.delete(`apps/${appId}`); - } - - public syncApp(appId: string): IAppSynced { - return APIClient.post(`/v1/apps/${appId}/sync`); + if ('app' in result) { + return result; + } + throw new Error('App not found'); } public async setAppStatus(appId: string, status: AppStatus): Promise { - const { status: effectiveStatus } = await APIClient.post(`/v1/apps/${appId}/status`, { status }); + const { status: effectiveStatus } = await APIClient.post(`/apps/${appId}/status`, { status }); return effectiveStatus; } - public enableApp(appId: string): Promise { - return this.setAppStatus(appId, AppStatus.MANUALLY_ENABLED); - } - public disableApp(appId: string): Promise { return this.setAppStatus(appId, AppStatus.MANUALLY_ENABLED); } - public buildExternalUrl(appId: string, purchaseType = 'buy', details = false): IAppExternalURL { - return APIClient.get('/v1/apps', { + public async buildExternalUrl(appId: string, purchaseType: 'buy' | 'subscription' = 'buy', details = false): Promise { + const result = await APIClient.get('/apps', { buildExternalUrl: 'true', appId, purchaseType, - details, + details: `${details}`, }); + + if ('url' in result) { + return result; + } + throw new Error('Failed to build external url'); } - public async getCategories(): Promise { - const categories = await APIClient.get('/v1/apps', { categories: 'true' }); - return categories; + public async getCategories(): Promise[]> { + const result = await APIClient.get('/apps', { categories: 'true' }); + if ('categories' in result) { + return result.categories; + } + throw new Error('Categories not found'); } public getUIHost(): RealAppsEngineUIHost { @@ -267,7 +274,7 @@ export const Apps = new AppClientOrchestrator(); Meteor.startup(() => { CachedCollectionManager.onLogin(() => { - Meteor.call('apps/is-enabled', (error: Error, isEnabled: boolean) => { + Meteor.call('/apps/is-enabled', (error: Error, isEnabled: boolean) => { if (error) { Apps.handleError(error); return; @@ -279,7 +286,7 @@ Meteor.startup(() => { }); Tracker.autorun(() => { - const isEnabled = settings.get('Apps_Framework_enabled'); + const isEnabled = settings.get('/Apps_Framework_enabled'); Apps.load(isEnabled); }); }); diff --git a/apps/meteor/client/lib/meteorCallWrapper.ts b/apps/meteor/client/lib/meteorCallWrapper.ts index 2615b6c4385..7368cb328e3 100644 --- a/apps/meteor/client/lib/meteorCallWrapper.ts +++ b/apps/meteor/client/lib/meteorCallWrapper.ts @@ -49,15 +49,13 @@ function wrapMeteorDDPCalls(): void { }); Meteor.connection.onMessage(_message); }; + const method = encodeURIComponent(message.method.replace(/\//g, ':')); - APIClient.post( - `/v1/${endpoint}/${encodeURIComponent(message.method.replace(/\//g, ':'))}` as Parameters[0], - restParams as any, - ) + APIClient.post(`/v1/${endpoint}/${method}`, restParams) .then(({ message: _message }) => { processResult(_message); if (message.method === 'login') { - const parsedMessage = DDPCommon.parseDDP(_message) as { result?: { token?: string } }; + const parsedMessage = DDPCommon.parseDDP(_message as any) as { result?: { token?: string } }; if (parsedMessage.result?.token) { Meteor.loginWithToken(parsedMessage.result.token); } diff --git a/apps/meteor/client/lib/userData.ts b/apps/meteor/client/lib/userData.ts index 112028faa0c..a75cc865f19 100644 --- a/apps/meteor/client/lib/userData.ts +++ b/apps/meteor/client/lib/userData.ts @@ -81,10 +81,35 @@ export const synchronizeUserData = async (uid: Meteor.User['_id']): Promise ({ + ...token, + when: new Date(token.when), + })), + }, + }), + }), + ...(lastLogin && { + lastLogin: new Date(lastLogin), + }), + ldap: Boolean(ldap), createdAt: new Date(userData.createdAt), _updatedAt: new Date(userData._updatedAt), }); diff --git a/apps/meteor/client/providers/ServerProvider.tsx b/apps/meteor/client/providers/ServerProvider.tsx index 18e6ac30768..50d288d73ec 100644 --- a/apps/meteor/client/providers/ServerProvider.tsx +++ b/apps/meteor/client/providers/ServerProvider.tsx @@ -30,20 +30,20 @@ const callEndpoint = >( ): Promise>>> => { switch (method) { case 'GET': - return APIClient.get(path as Parameters[0], params) as any; + return APIClient.get(path as Parameters[0], params as any | undefined) as any; case 'POST': - return APIClient.post(path as Parameters[0], params) as ReturnType; + return APIClient.post(path as Parameters[0], params as never) as ReturnType; case 'DELETE': - return APIClient.delete(path as Parameters[0], params) as ReturnType; + return APIClient.delete(path as Parameters[0], params as never) as ReturnType; default: throw new Error('Invalid HTTP method'); } }; -const uploadToEndpoint = (endpoint: PathFor<'POST'>, formData: any): Promise => APIClient.post(endpoint, formData); +const uploadToEndpoint = (endpoint: PathFor<'POST'>, formData: any): Promise => APIClient.post(endpoint, formData as never); const getStream = (streamName: string, options: {} = {}): ((eventName: string, callback: (data: T) => void) => () => void) => { const streamer = Meteor.StreamerCentral.instances[streamName] diff --git a/apps/meteor/client/views/admin/apps/AppDetailsPage.tsx b/apps/meteor/client/views/admin/apps/AppDetailsPage.tsx index 2d1748e392c..c78ece156ec 100644 --- a/apps/meteor/client/views/admin/apps/AppDetailsPage.tsx +++ b/apps/meteor/client/views/admin/apps/AppDetailsPage.tsx @@ -4,7 +4,7 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useTranslation, useCurrentRoute, useRoute, useRouteParameter } from '@rocket.chat/ui-contexts'; import React, { useState, useCallback, useRef, FC } from 'react'; -import { ISettings, ISettingsPayload } from '../../../../app/apps/client/@types/IOrchestrator'; +import { ISettings } from '../../../../app/apps/client/@types/IOrchestrator'; import { Apps } from '../../../../app/apps/client/orchestrator'; import Page from '../../../components/Page'; import APIsDisplay from './APIsDisplay'; @@ -49,7 +49,7 @@ const AppDetailsPage: FC<{ id: string }> = function AppDetailsPage({ id }) { (Object.values(settings || {}) as ISetting[]).map((value) => ({ ...value, value: current?.[value.id], - })) as unknown as ISettingsPayload, + })), ); } catch (e) { handleAPIError(e); diff --git a/apps/meteor/client/views/admin/apps/hooks/useCategories.ts b/apps/meteor/client/views/admin/apps/hooks/useCategories.ts index 075cc139f8c..61dfae4e299 100644 --- a/apps/meteor/client/views/admin/apps/hooks/useCategories.ts +++ b/apps/meteor/client/views/admin/apps/hooks/useCategories.ts @@ -1,7 +1,6 @@ import { useTranslation } from '@rocket.chat/ui-contexts'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { ICategory } from '../../../../../app/apps/client/@types/IOrchestrator'; import { Apps } from '../../../../../app/apps/client/orchestrator'; import { CategoryDropdownItem, CategoryDropDownListProps } from '../definitions/CategoryDropdownDefinitions'; import { handleAPIError } from '../helpers'; @@ -21,7 +20,7 @@ export const useCategories = (): [ try { const fetchedCategories = await Apps.getCategories(); - const mappedCategories = fetchedCategories.map((currentCategory: ICategory) => ({ + const mappedCategories = fetchedCategories.map((currentCategory) => ({ id: currentCategory.id, label: currentCategory.title, checked: false, diff --git a/packages/core-typings/src/IUser.ts b/packages/core-typings/src/IUser.ts index 9d0b4547d63..0dde24a2d7b 100644 --- a/packages/core-typings/src/IUser.ts +++ b/packages/core-typings/src/IUser.ts @@ -116,6 +116,7 @@ export interface IUser extends IRocketChatRecord { status?: UserStatus; statusConnection?: string; lastLogin?: Date; + bio?: string; avatarOrigin?: string; avatarETag?: string; utcOffset?: number; diff --git a/packages/rest-typings/src/apps/index.ts b/packages/rest-typings/src/apps/index.ts index 50b9160d23e..d7f6fd10c9b 100644 --- a/packages/rest-typings/src/apps/index.ts +++ b/packages/rest-typings/src/apps/index.ts @@ -1,13 +1,26 @@ import type { IApiEndpointMetadata } from '@rocket.chat/apps-engine/definition/api'; +import type { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; import type { IExternalComponent } from '@rocket.chat/apps-engine/definition/externalComponent'; +import type { IPermission } from '@rocket.chat/apps-engine/definition/permissions/IPermission'; +import type { ISetting } from '@rocket.chat/apps-engine/definition/settings'; import type { IUIActionButton } from '@rocket.chat/apps-engine/definition/ui'; -import type { ISetting, AppScreenshot, App } from '@rocket.chat/core-typings'; +import type { AppScreenshot, App } from '@rocket.chat/core-typings'; export type AppsEndpoints = { '/apps/externalComponents': { GET: () => { externalComponents: IExternalComponent[] }; }; + '/apps/:id': { + GET: (params: { marketplace?: 'true' | 'false'; version?: string; appVersion?: string; update?: 'true' | 'false' }) => { + app: App; + }; + DELETE: () => void; + POST: (params: { marketplace: boolean; version: string; permissionsGranted: IPermission[]; appId: string }) => { + app: App; + }; + }; + '/apps/actionButtons': { GET: () => IUIActionButton[]; }; @@ -18,8 +31,9 @@ export type AppsEndpoints = { '/apps/:id/settings': { GET: () => { - [key: string]: ISetting; + settings: { [key: string]: ISetting }; }; + POST: (params: { settings: ISetting[] }) => { updated: { [key: string]: ISetting } }; }; '/apps/:id/screenshots': { @@ -34,8 +48,49 @@ export type AppsEndpoints = { }; }; - '/apps/:id': { - GET: (params: { marketplace?: 'true' | 'false'; update?: 'true' | 'false'; appVersion: string }) => { + '/apps/bundles/:id/apps': { + GET: () => { + apps: App[]; + }; + }; + + '/apps/:id/sync': { + POST: () => { + app: App; + }; + }; + + '/apps/:id/status': { + POST: (params: { status: AppStatus }) => { + status: string; + }; + }; + + '/apps': { + GET: + | ((params: { buildExternalUrl: 'true'; purchaseType?: 'buy' | 'subscription'; appId?: string; details?: 'true' | 'false' }) => { + url: string; + }) + | ((params: { + purchaseType?: 'buy' | 'subscription'; + marketplace?: 'true' | 'false'; + version?: string; + appId?: string; + details?: 'true' | 'false'; + }) => { + apps: App[]; + }) + | ((params: { categories: 'true' | 'false' }) => { + categories: { + createdDate: string; + description: string; + id: string; + modifiedDate: Date; + title: string; + }[]; + }); + + POST: (params: { appId: string; marketplace: boolean; version: string; permissionsGranted: IPermission[] }) => { app: App; }; }; diff --git a/packages/rest-typings/src/v1/me.ts b/packages/rest-typings/src/v1/me.ts index af406ac1d4d..048bd03f1f9 100644 --- a/packages/rest-typings/src/v1/me.ts +++ b/packages/rest-typings/src/v1/me.ts @@ -1,35 +1,48 @@ -import type { IUser, Serialized } from '@rocket.chat/core-typings'; +import type { IUser } from '@rocket.chat/core-typings'; -type RawUserData = Serialized< - Pick< - IUser, - | '_id' - | 'type' - | 'name' - | 'username' - | 'emails' - | 'status' - | 'statusDefault' - | 'statusText' - | 'statusConnection' - | 'avatarOrigin' - | 'utcOffset' - | 'language' - | 'settings' - | 'roles' - | 'active' - | 'defaultRoom' - | 'customFields' - | 'statusLivechat' - | 'oauth' - | 'createdAt' - | '_updatedAt' - | 'avatarETag' - > ->; +type Keys = + | 'name' + | 'username' + | 'nickname' + | 'emails' + | 'status' + | 'statusDefault' + | 'statusText' + | 'statusConnection' + | 'bio' + | 'avatarOrigin' + | 'utcOffset' + | 'language' + | 'settings' + | 'idleTimeLimit' + | 'roles' + | 'active' + | 'defaultRoom' + | 'customFields' + | 'requirePasswordChange' + | 'requirePasswordChangeReason' + | 'services.github' + | 'services.gitlab' + | 'services.tokenpass' + | 'services.password.bcrypt' + | 'services.totp.enabled' + | 'services.email2fa.enabled' + | 'statusLivechat' + | 'banners' + | 'oauth.authorizedClients' + | '_updatedAt' + | 'avatarETag' + | 'extension'; export type MeEndpoints = { '/v1/me': { - GET: () => RawUserData; + GET: (params: { fields: Record | Record; user: IUser }) => IUser & { + email?: string; + settings: { + profile: {}; + preferences: unknown; + }; + avatarUrl: string; + }; }; }; diff --git a/packages/rest-typings/src/v1/misc.ts b/packages/rest-typings/src/v1/misc.ts index c2d36fcc83c..5271af72212 100644 --- a/packages/rest-typings/src/v1/misc.ts +++ b/packages/rest-typings/src/v1/misc.ts @@ -180,45 +180,35 @@ export type MiscEndpoints = { }[]; }; }; - 'me': { - GET: (params: { fields: { [k: string]: number }; user: IUser }) => IUser & { - email?: string; - settings: { - profile: {}; - preferences: unknown; - }; - avatarUrl: string; - }; - }; - 'shield.svg': { + '/v1/shield.svg': { GET: (params: ShieldSvg) => { svg: string; }; }; - 'spotlight': { + '/v1/spotlight': { GET: (params: Spotlight) => { users: Pick[]; rooms: IRoom[]; }; }; - 'directory': { + '/v1/directory': { GET: (params: Directory) => PaginatedResult<{ result: (IUser | IRoom | ITeam)[]; }>; }; - 'method.call': { - POST: (params: MethodCall) => { - result: unknown; + '/v1/method.call/:method': { + POST: (params: { message: string }) => { + message: unknown; }; }; - 'method.callAnon': { - POST: (params: MethodCallAnon) => { - result: unknown; + '/v1/method.callAnon/:method': { + POST: (params: { message: string }) => { + message: unknown; }; }; };