plugins: ctx.stop to stop middlewares

Massimo Melina committed Jun 5, 2024 at 11:30 UTC c7cb2227cb813b117737400b6ed31bcf01d3f274
2 files changed +27 -3
dev-plugins.md
+4 -2
@@ -80,7 +80,7 @@ used must be strictly JSON (thus, no single quotes, only double quotes for strin
80 You can also include external files, by entering a full URL. Multiple files can be specified as `['file1.css', 'file2.css']`.
81 - `frontend_js: string | string[]` path to one or more js files that you want the frontend to load. These are to be placed in the `public` folder (refer below).
82 You can also include external files, by entering a full URL.
83 -- `middleware: (Context) => Promisable<void | true | function>` a function that will be used as a middleware: use this to interfere with http activity.
83 +- `middleware: (Context) => Promisable<void | function>` a function that will be used as a middleware: use this to interfere with http activity.
84
85 ```js
86 exports.middleware = ctx => {
@@ -89,7 +89,7 @@ used must be strictly JSON (thus, no single quotes, only double quotes for strin
89 }
90 ```
91 You'll find more examples by studying plugins like `antidos` or `antibrute`.
92 - To interrupt other middlewares on this http request, return `true`.
92 + To interrupt other middlewares on this http request, call `ctx.stop()`.
93 If you want to execute something in the "upstream" of middlewares, return a function. This function can be async.
94 You can read more in [the ctx object](#the-ctx-object) section.
95
@@ -589,6 +589,8 @@ If you want to override a text regardless of the language, use the special langu
589 - HFS.html
590 - HFS.useSnapState
591 - HFS.debounceAsync
592 + - middleware: ctx.stop()
593 + - the old way of returning true is now deprecated
594 - 8.72 (v0.52.0)
595 - HFS.toast
596 - HFS.misc functions
src/plugins.ts
+23 -1
@@ -24,6 +24,7 @@ import { watchLoadCustomHtml } from './customHtml'
24 import { KvStorage, KvStorageOptions } from '@rejetto/kvstorage'
25 import { onProcessExit } from './first'
26 import { notifyClient } from './frontEndApis'
27 +import { app } from './index'
28
29 export const PATH = 'plugins'
30 export const DISABLING_SUFFIX = '-disabled'
@@ -116,6 +117,13 @@ async function initPlugin<T>(pl: any, morePassedToInit?: T) {
117 }))
118 }
119
120 +const already = new Set()
121 +function warnOnce(msg: string) {
122 + if (already.has(msg)) return
123 + already.add(msg)
124 + console.log('Warning: ' + msg)
125 +}
126 +
127 export const pluginsMiddleware: Koa.Middleware = async (ctx, next) => {
128 const after: Dict<CallMeAfter> = {}
129 // run middleware plugins
@@ -129,7 +137,11 @@ export const pluginsMiddleware: Koa.Middleware = async (ctx, next) => {
137 lastStatus = ctx.status
138 lastBody = ctx.body
139 }
132 - if (res === true)
140 + if (res === true) { // true for legacy pre-0.53
141 + ctx.stop()
142 + warnOnce(`plugin ${id} is using deprecated API (return true on middleware) and may not work with future versions (check for an update to "${id}")`)
143 + }
144 + if (ctx.isStopped)
145 console.debug("plugin blocked request", ctx.pluginBlockedRequest = id)
146 if (typeof res === 'function')
147 after[id] = res
@@ -164,6 +176,16 @@ function printError(id: string, e: any) {
176 console.debug(e)
177 }
178
179 +declare module "koa" {
180 + interface BaseContext {
181 + stop(): void
182 + }
183 +}
184 +events.once('app', () => Object.assign(app.context, {
185 + isStopped: false,
186 + stop() { this.isStopped = true }
187 +}))
188 +
189 // return false to ask to exclude this entry from results
190 interface OnDirEntryParams { entry:DirEntry, ctx:Koa.Context, node:VfsNode }
191 type OnDirEntry = (params:OnDirEntryParams) => void | false