plugins: doc for backend events
Massimo Melina committed
May 29, 2024 at 17:32 UTC
ad042f094a89f6b60ac0e080a793918e9548605a
4 files changed
+197
-127
dev-plugins.md
+194
-124
@@ -52,25 +52,25 @@ All the following properties are optional unless otherwise specified.
52
- `preview: string | string[]` one or more URLs to images you want to show before your plugin is downloaded. (JSON syntax)
53
- `depend: { repo: string, version: number }[]` declare what other plugins this depends on. (JSON syntax)
54
- `repo: string | object` pointer to a GitHub repo where this plugin is hosted. (JSON syntax)
55
- - the string form is for GitHub repos. Example: "rejetto/file-icons"
56
- - the object form will point to other custom repo. Object properties:
57
- - `web: string` link to a web page
58
- - `main: string` link to the plugin.js (can be relative to `web`)
59
- - `zip: string` link to the zip with the whole plugin (can be relative to `web`)
60
- - `zipRoot: string` optional, in case the plugin in the zip is inside a folder
61
-
62
- Example:
63
- ```
64
- {
65
- "web": "https://github.com/rejetto/file-icons",
66
- "zip": "/archive/refs/heads/main.zip",
67
- "zipRoot: "file-icons-main/dist",
68
- "main": "https://raw.githubusercontent.com/rejetto/file-icons/main/dist/plugin.js"
69
- }
70
- ```
71
- Note that in this example we are pointing to a github repo just for clarity. You are not supposed to use this
72
- complicated object form to link github, use the string form.
73
- Plugins with custom repos are not included in search results, but the update feature will still work.
55
+ - the string form is for GitHub repos. Example: "rejetto/file-icons"
56
+ - the object form will point to other custom repo. Object properties:
57
+ - `web: string` link to a web page
58
+ - `main: string` link to the plugin.js (can be relative to `web`)
59
+ - `zip: string` link to the zip with the whole plugin (can be relative to `web`)
60
+ - `zipRoot: string` optional, in case the plugin in the zip is inside a folder
61
+
62
+ Example:
63
+ ```
64
+ {
65
+ "web": "https://github.com/rejetto/file-icons",
66
+ "zip": "/archive/refs/heads/main.zip",
67
+ "zipRoot: "file-icons-main/dist",
68
+ "main": "https://raw.githubusercontent.com/rejetto/file-icons/main/dist/plugin.js"
69
+ }
70
+ ```
71
+ Note that in this example we are pointing to a github repo just for clarity. You are not supposed to use this
72
+ complicated object form to link github, use the string form.
73
+ Plugins with custom repos are not included in search results, but the update feature will still work.
74
75
WARNING: All the properties above are a bit special and must go in `exports` only (thus, not returned in `init`) and the syntax
76
used must be strictly JSON (thus, no single quotes, only double quotes for strings and objects), and must fit one line.
@@ -139,18 +139,18 @@ Based on `type`, other properties are supported:
139
- `options: { [label]: AnyJsonValue }`
140
- `multiselect` it's like `select` but its result is an array of values.
141
- `array` list of objects
142
- - `fields`: an object of `FieldDescriptor`s, i.e. same format as `config`.
143
- This field will be use for both the configuration of the grid's column, and the form's field.
144
- Other than properties of `FieldDescriptor` you get these extra properties:
145
- - `$column`: where you can put all the properties you want specifically to be set on the [grid's column](https://mui.com/x/api/data-grid/grid-col-def/).
146
- - `$width`: a shortcut property that can substitute `$column: { width }` or `$column: { flex }`.
147
- By default, a column gets flex:1 unless you specify $width. A value of 8 and higher is considered width's pixels,
148
- while lower are flex-values.
142
+ - `fields`: an object of `FieldDescriptor`s, i.e. same format as `config`.
143
+ This field will be use for both the configuration of the grid's column, and the form's field.
144
+ Other than properties of `FieldDescriptor` you get these extra properties:
145
+ - `$column`: where you can put all the properties you want specifically to be set on the [grid's column](https://mui.com/x/api/data-grid/grid-col-def/).
146
+ - `$width`: a shortcut property that can substitute `$column: { width }` or `$column: { flex }`.
147
+ By default, a column gets flex:1 unless you specify $width. A value of 8 and higher is considered width's pixels,
148
+ while lower are flex-values.
149
- `real_path` path to server disk
150
- - `files: boolean` allow to select a file. Default is `true`.
151
- - `folders: boolean` allow to select a folder. Default is `false`.
152
- - `defaultPath: string` what path to start from if no value is set. E.g. __dirname if you want to start with your plugin's folder.
153
- - `fileMask: string` restrict files that are displayed. E.g. `*.jpg|*.png`
150
+ - `files: boolean` allow to select a file. Default is `true`.
151
+ - `folders: boolean` allow to select a folder. Default is `false`.
152
+ - `defaultPath: string` what path to start from if no value is set. E.g. __dirname if you want to start with your plugin's folder.
153
+ - `fileMask: string` restrict files that are displayed. E.g. `*.jpg|*.png`
154
- `username`
155
156
## api object
@@ -162,7 +162,7 @@ The `api` object you get as parameter of the `init` contains the following:
162
- `setConfig(key: string, value: any)` set plugin's config value.
163
164
- `subscribeConfig(key: string, callback: (value: any) => void): Unsubscriber`
165
- will call `callback` with initial value and then at each change.
165
+ will call `callback` with initial value and then at each change.
166
167
- `getHfsConfig(key: string): any` similar to getConfig, but retrieves HFS' config instead.
168
@@ -173,20 +173,20 @@ The `api` object you get as parameter of the `init` contains the following:
173
- `getConnections: Connections[]` retrieve current list of active connections.
174
175
- `storageDir: string` folder where a plugin is supposed to store run-time data. This folder is preserved during
176
- an update of the plugin, while the rest could be deleted.
176
+ an update of the plugin, while the rest could be deleted.
177
178
-- `events` this is the main events emitter used by HFS.
179
- These are backend side events, not to be confused with frontend ones. It's not the standard EventEmitter class,
178
+- `events` this is the main events emitter used by HFS.
179
+ These are backend side events, not to be confused with frontend ones. It's not the standard EventEmitter class,
180
and the API is slightly different.
181
-
182
- - `events.on(name: string, listener: Callback): Callback`
183
-
184
- call your listener every time the event is emitted.
185
- The returned callback will unsubscribe the event.
186
-
187
- - `events.once(name: string, listener?: Callback): Promise<eventArguments>`
188
-
189
- when the event is emitted, your (optional) listener is called, and the returned promise is resolved.
181
+
182
+ - `events.on(name: string, listener: Callback): Callback`
183
+
184
+ call your listener every time the event is emitted.
185
+ The returned callback will unsubscribe the event.
186
+
187
+ - `events.once(name: string, listener?: Callback): Promise<eventArguments>`
188
+
189
+ when the event is emitted, your (optional) listener is called, and the returned promise is resolved.
190
191
- `require: function` use this instead of standard `require` function to access modules already loaded by HFS. Example:
192
```js
@@ -199,9 +199,9 @@ The `api` object you get as parameter of the `init` contains the following:
199
200
- `customApiCall: (method: string, ...params) => any[]` this will invoke other plugins if they define `method`
201
exported inside `customApi: object`
202
-
202
+
203
- `openDb: (filename, options) => Promise<{ get, put, del, close, unlink, sublevel }>` LevelDB-like class for storage.
204
- Refer to [dedicated documentation](https://www.npmjs.com/package/@rejetto/kvstorage) for details.
204
+ Refer to [dedicated documentation](https://www.npmjs.com/package/@rejetto/kvstorage) for details.
205
206
## Front-end specific
207
@@ -215,25 +215,25 @@ The HFS objects contains many properties:
215
- `useApi`
216
- `reloadList`
217
- `logout`
218
-- `prefixUrl: string` normally an empty string, it will be set in case a [reverse-proxy wants to mount HFS on a path](https://github.com/rejetto/hfs/wiki/Reverse-proxy).
218
+- `prefixUrl: string` normally an empty string, it will be set in case a [reverse-proxy wants to mount HFS on a path](https://github.com/rejetto/hfs/wiki/Reverse-proxy).
219
- `state` [object with many values in it](https://github.com/rejetto/hfs/blob/main/frontend/src/state.ts)
220
- `watchState: (key: string, callback)=>function`
221
- - watch the `key` property of the state object above
222
- - `callback(newValue)` will be called at each change
223
- - use returned callback to stop watching
221
+ - watch the `key` property of the state object above
222
+ - `callback(newValue)` will be called at each change
223
+ - use returned callback to stop watching
224
- `React` whole React object, as for `require('react')` (JSX syntax is not supported here)
225
- `h` shortcut for React.createElement
226
- `t` [translator function](https://github.com/rejetto/hfs/blob/main/frontend/src/i18n.ts)
227
- `_` [lodash library](https://lodash.com/docs/)
228
- `toast: (message: string | ReactElement, type: ToastType='info')`
229
- - show a brief message that doesn't steal focus
230
- - `ToastType = 'error' | 'warning' | 'info' | 'success'`
229
+ - show a brief message that doesn't steal focus
230
+ - `ToastType = 'error' | 'warning' | 'info' | 'success'`
231
- `dialogLib` this exposes all functions available in [dialog.ts](https://github.com/rejetto/hfs/blob/main/frontend/src/dialog.ts), for example alertDialog and newDialog. These are not documented yet, and subject to change without notification, but you can study the sources if you are interested in using them.
232
- `misc` many functions and constants available in [cross.ts](https://github.com/rejetto/hfs/blob/main/src/cross.ts). These are not documented, probably never will, and are subject to change without notifications, but you can study the sources if you are interested in using them.
233
- `navigate: (uri: string): void` use this if you have to change the page address without causing reload
234
-- `emit: (name: string, params?: object) => any[]` use this to emit a custom event. Prefix name with your plugin name to avoid conflicts.
234
+- `emit: (name: string, params?: object) => any[]` use this to emit a custom event. Prefix name with your plugin name to avoid conflicts.
235
- `Icon: ReactComponent` Properties:
236
- - `name: string` refer to file `icons.ts` for names, but you can also enter an emoji instead.
236
+ - `name: string` refer to file `icons.ts` for names, but you can also enter an emoji instead.
237
- `useBatch: (worker, job) => any`
238
239
The following properties are accessible only immediately at top-level; don't call it later in a callback.
@@ -259,11 +259,11 @@ Some frontend-events can return Html, which can be expressed in several ways
259
- as DOM Nodes, as for document.createElement()
260
- as ReactElement
261
- as array of ReactNode
262
-- null, undefined, false and empty-string will just be discarded
262
+- null, undefined, false and empty-string will just be discarded
263
264
These events will receive a `def` property, with the default content that will be displayed if no callback return
265
a valid output. You can decide to embed such default content inside your content.
266
-You can produce output for such events also by adding sections (with same name as the event) to file `custom.html`.
266
+You can produce output for such events also by adding sections (with same name as the event) to file `custom.html`.
267
268
This is a list of available frontend-events, with respective object parameter and output.
269
@@ -288,9 +288,9 @@ This is a list of available frontend-events, with respective object parameter an
288
- `getDefaultIcon: ()=>ReactElement` produces the default icon for this entry
289
- output `Html`
290
- `entry`
291
- - you receive each entry of the list, and optionally produce HTML code that will completely replace the entry row/slot.
292
- - parameter `{ entry: Entry }` (refer above for Entry object)
293
- - output `Html`
291
+ - you receive each entry of the list, and optionally produce HTML code that will completely replace the entry row/slot.
292
+ - parameter `{ entry: Entry }` (refer above for Entry object)
293
+ - output `Html`
294
- `afterEntryName`
295
- you receive each entry of the list, and optionally produce HTML code that will be added after the name of the entry.
296
- parameter `{ entry: Entry }` (refer above for Entry object)
@@ -307,7 +307,7 @@ This is a list of available frontend-events, with respective object parameter an
307
- output `Html`
308
- `fileMenu`
309
- add or manipulate entries of the menu. If you return something, that will be added to the menu.
310
- You can also delete or replace the content of the `menu` array.
310
+ You can also delete or replace the content of the `menu` array.
311
- parameter `{ entry: Entry, menu: FileMenuEntry[], props: FileMenuProp[] }`
312
- output `undefined | FileMenuEntry | FileMenuEntry[]`
313
```typescript
@@ -332,18 +332,88 @@ This is a list of available frontend-events, with respective object parameter an
332
```
333
or if you like lodash, you can simply `HFS._.remove(menu, { id: 'show' })`
334
- `fileShow`
335
- - you receive an entry of the list, and optionally produce React Component for visualization.
336
- - parameter `{ entry: Entry }` (refer above for Entry object)
337
- - output `ReactComponent`
335
+ - you receive an entry of the list, and optionally produce React Component for visualization.
336
+ - parameter `{ entry: Entry }` (refer above for Entry object)
337
+ - output `ReactComponent`
338
- `menuZip`
339
- - parameter `{ def: ReactNode }`
340
- - output `Html`
339
+ - parameter `{ def: ReactNode }`
340
+ - output `Html`
341
- `userPanelAfterInfo`
342
- - no parameter
343
- - output `Html`
342
+ - no parameter
343
+ - output `Html`
344
- `uriChanged`
345
- - DEPRECATED: use `watchState('uri', callback)` instead.
346
- - parameter `{ uri: string, previous: string }`
345
+ - DEPRECATED: use `watchState('uri', callback)` instead.
346
+ - parameter `{ uri: string, previous: string }`
347
+
348
+## Back-end events
349
+
350
+These events happen in the server, and not in the browser.
351
+You can listen to these events accessing `api.events` in the `init` function of the plugin.
352
+E.g.:
353
+```js
354
+exports.init = function(api) {
355
+ const cancelListening = api.events.on('spam', () => 'spam received!')
356
+ // pass the canceller callback to the 'unload', so the subscription will be correctly disposed when the plugin is stopped
357
+ return { unload: cancelListening }
358
+}
359
+```
360
+
361
+Of course the example above can be written more shortly as follows, but they are equivalent.
362
+
363
+```js
364
+exports.init = api => ({
365
+ unload: api.events.on('spam', () => 'spam received!')
366
+})
367
+```
368
+
369
+### Async
370
+
371
+Only where specified, events support async listeners, like
372
+```js
373
+api.events.on('deleting', async () => your-code-here)
374
+```
375
+
376
+### Stop, the way you prevent default behavior
377
+
378
+Some events allow you to stop their default behavior, by returning `false`.
379
+This is reported in the list below with the word "stoppable".
380
+
381
+```js
382
+api.events.on('deleting', ({ node }) => node.source.endsWith('.jpg'))
383
+```
384
+
385
+The example above will return false only when the file is NOT ending with .jpg, thus allowing only jpg files to be deleted.
386
+
387
+### Available events
388
+
389
+This section is still partially documented, and you may need to have a look at the sources for further details.
390
+
391
+- `deleting`
392
+ - parameters: { node, ctx }
393
+ - async supported
394
+ - stoppable
395
+- `logout`
396
+- `config ready`
397
+- `config.KEY` where KEY is the key of a config that has changed
398
+- `connectionClosed`
399
+- `connection`
400
+- `connectionUpdated`
401
+- `console`
402
+- `dynamicDnsError`
403
+- `httpsReady`
404
+- `spam`
405
+- `log`
406
+- `error_log`
407
+- `failedLogin`
408
+- `accountRenamed`
409
+- `pluginDownload`
410
+- `pluginUpdated`
411
+- `pluginInstalled`
412
+- `pluginUninstalled`
413
+- `pluginStopped`
414
+- `pluginStarted`
415
+- `uploadStart`
416
+- `uploadFinished`
417
418
## Other files
419
@@ -354,7 +424,7 @@ These files have a special meaning:
424
425
- `public` folder, and its files will be accessible at `/~/plugins/PLUGIN_NAME/FILENAME`
426
- `custom.html` file, that works exactly like the main `custom.html`. Even when same section is specified
357
- by 2 (or more) files, both contents are appended.
427
+ by 2 (or more) files, both contents are appended.
428
429
## Dependencies
430
@@ -396,7 +466,7 @@ Most React developers are used to JSX, which is not (currently) supported here.
466
If you want, you can try solutions to JSX support, like transpiling.
467
Anyway, React is not JSX, and can be easily used without.
468
399
-Any time in JSX you do
469
+Any time in JSX you do
470
```jsx
471
<button onClick={() => console.log('hi')}>Say hi</button>
472
```
@@ -432,77 +502,77 @@ HFS._.set(HFS.lang, 'en.translate.Options', 'Settings')
502
```
503
504
This works because all translations are stored inside `HFS.lang`.
435
-Using `HFS._.set` is not necessary, but in this case is convenient, because the language-code key may not exist.
505
+Using `HFS._.set` is not necessary, but in this case is convenient, because the language-code key may not exist.
506
437
-If you want to override a text regardless of the language, use the special language-code `all`.
507
+If you want to override a text regardless of the language, use the special language-code `all`.
508
509
## API version history
510
511
- 8.82 (v0.53.0)
442
- - api.openDb
443
- - frontend event: menuZip
444
- - config.type:username
445
- - api.events class has changed
446
- - frontend event "fileMenu": changed props format
512
+ - api.openDb
513
+ - frontend event: menuZip
514
+ - config.type:username
515
+ - api.events class has changed
516
+ - frontend event "fileMenu": changed props format
517
- 8.72 (v0.52.0)
448
- - HFS.toast
449
- - HFS.misc functions
450
- - HFS.state.uri
451
- - ~~frontend event: uriChanged~~
518
+ - HFS.toast
519
+ - HFS.misc functions
520
+ - HFS.state.uri
521
+ - ~~frontend event: uriChanged~~
522
- 8.65 (v0.51.0)
453
- - plugin's own hfs-lang files
454
- - HFS.state.props.can_overwrite
455
- - ctx.state.considerAsGui
456
- - frontend event: userPanelAfterInfo
457
- - breaking: moved custom properties from ctx to ctx.state
458
- - HFS.navigate
459
- - internationalization
523
+ - plugin's own hfs-lang files
524
+ - HFS.state.props.can_overwrite
525
+ - ctx.state.considerAsGui
526
+ - frontend event: userPanelAfterInfo
527
+ - breaking: moved custom properties from ctx to ctx.state
528
+ - HFS.navigate
529
+ - internationalization
530
- 8.5 (v0.49.0)
461
- - frontend event: entry
462
- - exports.onDirEntry: entry.icon
463
- - customApiCall supports any number of parameters
531
+ - frontend event: entry
532
+ - exports.onDirEntry: entry.icon
533
+ - customApiCall supports any number of parameters
534
- 8.4 (v0.48.2)
465
- - HFS.fileShow
466
- - api.Const (api.const is now deprecated)
535
+ - HFS.fileShow
536
+ - api.Const (api.const is now deprecated)
537
- 8.3 (v0.47.0)
468
- - HFS.useBatch
469
- - FileMenuEntry.id, .subLabel
538
+ - HFS.useBatch
539
+ - FileMenuEntry.id, .subLabel
540
- 8.23 (v0.46.0)
471
- - entry.getNext, getPrevious, getNextFiltered, getPreviousFiltered, getDefaultIcon
472
- - platform-dependent distribution
473
- - HFS.watchState, emit, useApi
474
- - api.storageDir, customApiCall
475
- - exports.depend
476
- - frontend event: fileShow
541
+ - entry.getNext, getPrevious, getNextFiltered, getPreviousFiltered, getDefaultIcon
542
+ - platform-dependent distribution
543
+ - HFS.watchState, emit, useApi
544
+ - api.storageDir, customApiCall
545
+ - exports.depend
546
+ - frontend event: fileShow
547
- 8.1 (v0.45.0) should have been 0.44.0 but forgot to update number
478
- - full URL support for frontend_js and frontend_css
479
- - custom.html
480
- - entry.cantOpen, ext, isFolder
481
- - HFS.apiCall, reloadList, logout, h, React, state, t, _, dialogLib, Icon, getPluginPublic
482
- - second parameter of onEvent is now deprecated
483
- - renamed: additionalEntryProps > additionalEntryDetails & entry-props > entry-details
484
- - frontend event: entryIcon
548
+ - full URL support for frontend_js and frontend_css
549
+ - custom.html
550
+ - entry.cantOpen, ext, isFolder
551
+ - HFS.apiCall, reloadList, logout, h, React, state, t, _, dialogLib, Icon, getPluginPublic
552
+ - second parameter of onEvent is now deprecated
553
+ - renamed: additionalEntryProps > additionalEntryDetails & entry-props > entry-details
554
+ - frontend event: entryIcon
555
- 8 (v0.43.0)
486
- - entry.name & .uri
487
- - tools.dialogLib
488
- - HFS.getPluginConfig()
556
+ - entry.name & .uri
557
+ - tools.dialogLib
558
+ - HFS.getPluginConfig()
559
- 7 (v0.42.0)
490
- - frontend event: fileMenu
491
- - HFS.SPECIAL_URI, PLUGINS_PUB_URI, FRONTEND_URI,
560
+ - frontend event: fileMenu
561
+ - HFS.SPECIAL_URI, PLUGINS_PUB_URI, FRONTEND_URI,
562
- 6 (v0.38.0)
493
- - config.frontend
563
+ - config.frontend
564
- 5 (v0.33.0)
495
- - frontend event: afterEntryName
565
+ - frontend event: afterEntryName
566
- 4.1 (v0.23.4)
497
- - config.type:array added $width, $column and fixed height
567
+ - config.type:array added $width, $column and fixed height
568
- 4 (v0.23.0)
499
- - config.type:real_path
500
- - api.subscribeConfig
501
- - api.setConfig
502
- - api.getHfsConfig
569
+ - config.type:real_path
570
+ - api.subscribeConfig
571
+ - api.setConfig
572
+ - api.getHfsConfig
573
- 3 (v0.21.0)
504
- - config.defaultValue
505
- - async for init/unload
506
- - api.log
574
+ - config.defaultValue
575
+ - async for init/unload
576
+ - api.log
577
- 2
508
- - config.type:array
578
+ - config.type:array
src/acme.ts
+1
-1
@@ -111,7 +111,7 @@ export const makeCert = debounceAsync(async (domain: string, email?: string, alt
111
const acmeDomain = defineConfig('acme_domain', '')
112
const acmeEmail = defineConfig('acme_email', '')
113
const acmeRenew = defineConfig('acme_renew', false) // handle config changes
114
-events.once('https ready', () => repeat(HOUR, renewCert))
114
+events.once('httpsReady', () => repeat(HOUR, renewCert))
115
116
// checks if the cert is near expiration date, and if so renews it
117
const renewCert = debounceAsync(async () => {
src/config.ts
+1
-1
@@ -137,7 +137,7 @@ export function setConfig(newCfg: Record<string,unknown>, save?: boolean) {
137
if (!newCfg.hasOwnProperty(k))
138
apply(k, newCfg[k], true)
139
started = true
140
- events.emit('config ready')
140
+ events.emit('configReady')
141
if (version !== VERSION) // be sure to save version
142
saveConfigAsap()
143
src/listen.ts
+1
-1
@@ -132,7 +132,7 @@ const considerHttps = debounceAsync(async () => {
132
if (!port) return
133
httpsSrv.on('connection', newConnection)
134
printUrls(httpsSrv.name)
135
- events.emit('https ready')
135
+ events.emit('httpsReady')
136
defaultBaseUrl.proto = 'https'
137
defaultBaseUrl.port = getCurrentPort(httpsSrv) ?? 0
138
})