plugins: api.onServer

Massimo Melina committed Jul 29, 2025 at 12:04 UTC bb1977ed31baaf72ba4997159372c811a9c01336
4 files changed +35 -24
dev-plugins.md
+10 -3
@@ -356,6 +356,9 @@ The `api` object you get as parameter of the `init` contains the following:
356
357 - `setInterval`, `setTimeout` same as standard js functions, but will automatically cancel if the plugin is unloaded.
358
359 +- `onServer(cb: (Server) => any)` execute your callback on every instance of Server created by HFS.
360 + It is the standard Node.js class, and it can be http or https. It can be instantiated multiple times.
361 +
362 ## Frontend JS
363
364 The following information applies to the frontend bundled with HFS.
@@ -728,7 +731,6 @@ This section is still partially documented, and you may need to have a look at t
731 to let the default behavior while getting the content of the list, return a function, and it will be called for each
732 entry, passed as first parameter (an object of standard class fs.Dirent), and when the list is over it will be called
733 with a boolean, true if the list is completed and false if it was aborted
731 -
734
735 # Notifications (backend-to-frontend events)
736
@@ -820,6 +822,9 @@ You can decide if you want to use some building system/transpiler, but you'll ha
822 While you may just put a zip on any website, that would require manual installation.
823 If you want to appear in the Admin-panel, for easier finding and installation, please do as follows.
824
825 +Be sure that you are exporting (not returning) the essential properties, like `apiRequired`.
826 +Find the full list in the [[Things a plugin can export]], marked with "JSON syntax".
827 +
828 Suggested method for publishing is to have a dedicated repository on GitHub, with topic `hfs-plugin`.
829 To set the topic go on the repo home and click on the gear icon near the "About" box.
830 Be sure to also fill the "exports.description" field, especially with words that people may search for.
@@ -841,7 +846,7 @@ You can refer to these published plugins for reference, like
846 - https://github.com/rejetto/simple-player/
847 - https://github.com/rejetto/theme-example/
848
844 -Published plugins are required to specify the `apiRequired` property.
849 +Published plugins to have `exports.apiRequired`.
850
851 ### Multiple versions
852
@@ -1036,4 +1041,6 @@ If you want to override a text regardless of the language, use the special langu
1041 - HFS.fileShow return value
1042 - HFS.isShowSupported
1043 - 12.6 (v0.57.6)
1039 - - HFS.textSortCompare
\ No newline at end of file
1044 + - HFS.textSortCompare
1045 +- 12.7 (v0.57.10)
1046 + - api.onServer
\ No newline at end of file
src/const.ts
+1 -1
@@ -9,7 +9,7 @@ import { formatTimestamp } from './cross'
9 import { argv } from './argv'
10 export * from './cross-const'
11
12 -export const API_VERSION = 12.6
12 +export const API_VERSION = 12.7
13 export const COMPATIBLE_API_VERSION = 1 // while changes in the api are not breaking, this number stays the same, otherwise it is made equal to API_VERSION
14
15 // you can add arguments with this file, currently used for the update process on mac/linux
src/listen.ts
+3 -2
@@ -145,8 +145,6 @@ const considerHttps = debounceAsync(async () => {
145 console.error("failed to create https server: check your private key and certificate", e.message)
146 return
147 }
148 - port = await startServer(httpsSrv, { port, host: listenInterface.get() })
149 - if (!port) return
148 httpsSrv.on('connection', newConnection) // this event is emitted as soon as the tcp layer is connected
149 httpsSrv.on('secureConnection', (socket: TLSSocket) => { // emitted when the TLS layer is connected
150 for (const c of getConnections()) // TLSSocket shares same ip:port, so we can find its matching Connection
@@ -154,6 +152,8 @@ const considerHttps = debounceAsync(async () => {
152 && socket.remotePort === c.socket.remotePort)
153 return c.socket.emit('secure', socket) // let know Connection about the secure socket
154 })
155 + port = await startServer(httpsSrv, { port, host: listenInterface.get() })
156 + if (!port) return
157 printUrls(httpsSrv.name)
158 events.emit('httpsReady')
159 defaultBaseUrl.proto = 'https'
@@ -237,6 +237,7 @@ export function startServer(srv: typeof httpSrv, { port, host }: StartServer) {
237 return reject('type of socket not supported')
238 }
239 srv.removeListener('error', onError) // necessary in case someone calls stop/start many times
240 + events.emit('listening', { server: srv, port: ad.port })
241 resolve(ad.port)
242 })
243
src/plugins.ts
+21 -18
@@ -33,6 +33,7 @@ import { i18nFromTranslations } from './i18n'
33 import { addAccount, ctxBelongsTo, delAccount, getAccount, getUsernames, renameAccount, updateAccount } from './perm'
34 import { getCurrentUsername } from './auth'
35 import { CustomizedIcons, watchIconsFolder } from './icons'
36 +import { getServerStatus } from './listen'
37
38 export const PATH = 'plugins'
39 export const DISABLING_SUFFIX = '-disabled'
@@ -123,18 +124,17 @@ export function getPluginConfigFields(id: string) {
124 async function initPlugin(pl: any, morePassedToInit?: { id: string } & Dict<any>) {
125 const undoEvents: any[] = []
126 const timeouts: NodeJS.Timeout[] = []
127 + const controlledEvents = Object.create(events, objFromKeys(['on', 'once', 'multi'], k => ({
128 + value() {
129 + const ret = (events[k] as any)(...arguments)
130 + undoEvents.push(ret)
131 + return ret
132 + }
133 + })))
134 const res = await pl.init?.({
127 - Const,
128 - require,
129 - getConnections,
135 + Const, require,
136 // intercept all subscriptions, so to be able to undo them on unload
131 - events: Object.create(events, objFromKeys(['on', 'once', 'multi'], k => ({
132 - value() {
133 - const ret = (events[k] as any)(...arguments)
134 - undoEvents.push(ret)
135 - return ret
136 - }
137 - }))),
137 + events: controlledEvents,
138 log: console.log,
139 setError(msg: string) { setError(morePassedToInit?.id || 'server_code', msg) },
140 getHfsConfig: getConfig,
@@ -148,14 +148,17 @@ async function initPlugin(pl: any, morePassedToInit?: { id: string } & Dict<any>
148 timeouts.push(ret)
149 return ret
150 },
151 - customApiCall,
152 - notifyClient,
153 - addBlock,
154 - misc,
155 - _,
156 - ctxBelongsTo,
157 - getCurrentUsername,
158 - getAccount, getUsernames, addAccount, delAccount, updateAccount, renameAccount,
151 + async onServer(cb: Callback<object>) {
152 + const res = await getServerStatus()
153 + if (res.http.srv)
154 + cb(res.http.srv)
155 + if (res.https.srv)
156 + cb(res.https.srv)
157 + controlledEvents.on('listening', ({ server }: any) => cb(server))
158 + },
159 + misc, _,
160 + customApiCall, notifyClient, addBlock, ctxBelongsTo, getConnections,
161 + getCurrentUsername, getAccount, getUsernames, addAccount, delAccount, updateAccount, renameAccount,
162 ...morePassedToInit
163 })
164 Object.assign(pl, typeof res === 'function' ? { unload: res } : res)