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