@samitouri / QOSami-HFS / commits / b82f27ea

plugins: HFS.customRestCall

Massimo Melina committed Aug 27, 2024 at 00:36 UTC b82f27ea710ad441e0402ae3a7cc25ae8508e2f8
5 files changed +26 -2
dev-plugins.md
+3
@@ -120,6 +120,7 @@ used must be strictly JSON (thus, no single quotes, only double quotes for strin
120 - `configDialog: DialogOptions` object to override dialog options. Please refer to sources for details.
121 - `onFrontendConfig: (config: object) => void | object` manipulate config values exposed to front-end.
122 - `customHtml: object | () => object` return custom-html sections programmatically.
123 +- `customRest: { [name]: (parameters: object) => any }` declare backend functions to be called by frontend with `HFS.customRestCall`
124
125 ### FieldDescriptor
126
@@ -267,6 +268,7 @@ The HFS objects contains many properties:
268 - `debounceAsync: function` like lodash.debounce, but also avoids async invocations to overlap.
269 For details please refer to `src/debounceAsync.ts`.
270 - `loadScript(uri: string): Promise` load a js file. If uri is relative, it is based on the plugin's public folder.
271 +- `customRestCall(name: string, parameters?: object): Promise<any>` call backend functions exported with `customRest`.
272
273 The following properties are accessible only immediately at top-level; don't call it later in a callback.
274 - `getPluginConfig()` returns object of all config keys that are declared frontend-accessible by this plugin.
@@ -631,6 +633,7 @@ If you want to override a text regardless of the language, use the special langu
633 - api.addBlock
634 - api.misc
635 - frontend event: paste
636 + - exports.customRest + HFS.customRestCall
637 - 8.891 (v0.53.0)
638 - api.openDb
639 - frontend event: menuZip
frontend/src/misc.ts
+3
@@ -68,6 +68,9 @@ const tools = {
68 watchState(k: string, cb: (v: any) => void) {
69 const up = k.split('upload.')[1]
70 return subscribeKey(up ? uploadState : state as any, up || k, cb, true)
71 + },
72 + customRestCall(name: string, ...rest: any[]) {
73 + return apiCall(cross.PLUGIN_CUSTOM_REST_PREFIX + name, ...rest)
74 }
75 }
76 Object.assign(getHFS(), {
src/apiMiddleware.ts
+5 -2
@@ -4,8 +4,9 @@ import Koa from 'koa'
4 import createSSE from './sse'
5 import { Readable } from 'stream'
6 import { asyncGeneratorToReadable, CFG, Promisable } from './misc'
7 -import { HTTP_BAD_REQUEST, HTTP_FOOL, HTTP_NOT_FOUND } from './const'
7 +import { HTTP_BAD_REQUEST, HTTP_FOOL, HTTP_NOT_FOUND, PLUGIN_CUSTOM_REST_PREFIX } from './const'
8 import { defineConfig } from './config'
9 +import { firstPlugin } from './plugins'
10
11 export class ApiError extends Error {
12 constructor(public status:number, message?:string | Error | object) {
@@ -31,7 +32,9 @@ export function apiMiddleware(apis: ApiHandlers) : Koa.Middleware {
32 || apiName.startsWith('get_') // "get_" apis are safe because they make no change
33 if (!safe)
34 return send(HTTP_FOOL, "missing header x-hfs-anti-csrf=1")
34 - const apiFun = apis.hasOwnProperty(apiName) && apis[apiName]!
35 + const customApiRest = apiName.startsWith(PLUGIN_CUSTOM_REST_PREFIX) && apiName.slice(PLUGIN_CUSTOM_REST_PREFIX.length)
36 + const apiFun = customApiRest && firstPlugin(pl => pl.getData().customRest?.[customApiRest])
37 + || apis.hasOwnProperty(apiName) && apis[apiName]!
38 if (!apiFun)
39 return send(HTTP_NOT_FOUND, 'invalid api')
40 // we don't rely on SameSite cookie option because it's https-only
src/cross-const.ts
+1
@@ -5,6 +5,7 @@ export const API_URI = SPECIAL_URI + 'api/'
5 export const PLUGINS_PUB_URI = SPECIAL_URI + 'plugins/'
6 export const PORT_DISABLED = -1
7 export const NBSP = '\xA0'
8 +export const PLUGIN_CUSTOM_REST_PREFIX = '_'
9
10 export const HTTP_OK = 200
11 export const HTTP_NO_CONTENT = 204
src/plugins.ts
+14
@@ -282,6 +282,20 @@ export function mapPlugins<T>(cb:(plugin:Readonly<Plugin>, pluginName:string)=>
282 }).filter(x => x !== undefined) as Exclude<T,undefined>[]
283 }
284
285 +export function firstPlugin<T>(cb:(plugin:Readonly<Plugin>, pluginName:string)=> T, includeServerCode=true) {
286 + for (const [plName,pl] of Object.entries(plugins)) {
287 + if (!includeServerCode && plName === SERVER_CODE_ID) continue
288 + try {
289 + const ret = cb(pl,plName)
290 + if (ret !== undefined)
291 + return ret
292 + }
293 + catch(e) {
294 + console.log('plugin error', plName, String(e))
295 + }
296 + }
297 +}
298 +
299 type PluginMiddleware = (ctx:Koa.Context) => Promisable<void | Stop | CallMeAfter>
300 type Stop = true
301 type CallMeAfter = ()=>any