mikser-io 11.4.0 → 11.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/routes.js +49 -0
- package/src/server.js +55 -1
package/package.json
CHANGED
package/src/routes.js
CHANGED
|
@@ -56,6 +56,23 @@ export function routeLocation(displayPath) {
|
|
|
56
56
|
return origin ? `${origin}${displayPath}` : displayPath
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
// The registered route a request path falls inside, or null.
|
|
60
|
+
//
|
|
61
|
+
// Longest prefix wins, so a mount nested under another ('/drive/notes' under
|
|
62
|
+
// '/drive') answers for its own requests rather than its parent's. Exported
|
|
63
|
+
// because the CORS middleware needs the same answer Express will reach, and a
|
|
64
|
+
// second implementation of "which mount is this" would drift from this one.
|
|
65
|
+
export function routeFor(requestPath) {
|
|
66
|
+
if (!requestPath) return null
|
|
67
|
+
let best = null
|
|
68
|
+
for (const route of runtime.routes ?? []) {
|
|
69
|
+
const base = route.path
|
|
70
|
+
if (requestPath !== base && !requestPath.startsWith(`${base}/`)) continue
|
|
71
|
+
if (!best || base.length > best.path.length) best = route
|
|
72
|
+
}
|
|
73
|
+
return best
|
|
74
|
+
}
|
|
75
|
+
|
|
59
76
|
// Declare a mounted route. Records the proxy-relevant descriptor on
|
|
60
77
|
// runtime.routes AND emits the standard mount log.
|
|
61
78
|
//
|
|
@@ -70,6 +87,26 @@ export function routeLocation(displayPath) {
|
|
|
70
87
|
// facade must disable buffering for it (Caddy
|
|
71
88
|
// flush_interval -1, nginx proxy_buffering off).
|
|
72
89
|
// Default false.
|
|
90
|
+
// cors `false` to emit no CORS headers for this mount. The
|
|
91
|
+
// same word as the global `--no-cors` /
|
|
92
|
+
// `config.server.cors: false`, meaning the same thing,
|
|
93
|
+
// scoped to one route. Default undefined — inherit the
|
|
94
|
+
// global setting.
|
|
95
|
+
//
|
|
96
|
+
// For a mount whose clients are not browsers: an OS
|
|
97
|
+
// WebDAV redirector, a CLI tool and curl never consult
|
|
98
|
+
// CORS, so a header naming who may read the endpoint
|
|
99
|
+
// from a web page is a claim with no beneficiary — and
|
|
100
|
+
// on an authenticated endpoint it is better made
|
|
101
|
+
// deliberately than inherited.
|
|
102
|
+
// methods the HTTP verbs this mount actually serves. Two
|
|
103
|
+
// consequences, and the second one is load-bearing:
|
|
104
|
+
// CORS advertises these for the route instead of a
|
|
105
|
+
// fixed five, and listing OPTIONS declares that the
|
|
106
|
+
// mount answers OPTIONS ITSELF — so the global
|
|
107
|
+
// preflight steps aside rather than terminating it.
|
|
108
|
+
// Default null, meaning "ordinary REST verbs, and the
|
|
109
|
+
// preflight is CORS's to answer".
|
|
73
110
|
// label log prefix. Defaults to `plugin`.
|
|
74
111
|
// detail optional already-formatted log suffix, e.g.
|
|
75
112
|
// '(ops=[list,subscribe])'.
|
|
@@ -99,6 +136,8 @@ export function registerRoute({
|
|
|
99
136
|
plugin,
|
|
100
137
|
reachability = 'public',
|
|
101
138
|
streaming = false,
|
|
139
|
+
methods = null,
|
|
140
|
+
cors,
|
|
102
141
|
label,
|
|
103
142
|
detail,
|
|
104
143
|
displayPath,
|
|
@@ -112,7 +151,17 @@ export function registerRoute({
|
|
|
112
151
|
)
|
|
113
152
|
}
|
|
114
153
|
|
|
154
|
+
if (methods != null && (!Array.isArray(methods) || methods.some(m => typeof m !== 'string'))) {
|
|
155
|
+
throw new Error('registerRoute: `methods` must be an array of verb strings')
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (cors !== undefined && cors !== false) {
|
|
159
|
+
throw new Error('registerRoute: `cors` accepts only false — omit it to inherit the global setting')
|
|
160
|
+
}
|
|
161
|
+
|
|
115
162
|
const descriptor = { path, plugin, reachability, streaming }
|
|
163
|
+
if (methods) descriptor.methods = methods.map(m => m.toUpperCase())
|
|
164
|
+
if (cors === false) descriptor.cors = false
|
|
116
165
|
|
|
117
166
|
// Dedup by path — a re-register (same path) replaces rather than
|
|
118
167
|
// duplicates. Mounts happen once per process, but this keeps the
|
package/src/server.js
CHANGED
|
@@ -28,6 +28,7 @@ import { createServer } from 'node:net'
|
|
|
28
28
|
import runtime from './runtime.js'
|
|
29
29
|
import { useLogger } from './engine/index.js'
|
|
30
30
|
import { onInitialized, onLoad, onLoaded } from './lifecycle.js'
|
|
31
|
+
import { routeFor } from './routes.js'
|
|
31
32
|
|
|
32
33
|
// Every non-loopback IPv4 address this machine answers on.
|
|
33
34
|
//
|
|
@@ -217,11 +218,64 @@ export function setupServer() {
|
|
|
217
218
|
// `origin: false` is how the cors package emits no CORS headers at all, which is
|
|
218
219
|
// what --no-cors / config.server.cors:false asks for.
|
|
219
220
|
if (!configured) return callback(null, { origin: false })
|
|
221
|
+
|
|
222
|
+
// The mount this request falls inside, if any. Asked per request
|
|
223
|
+
// because routes are registered at onLoaded and this middleware
|
|
224
|
+
// at onLoad — it is mounted before any of them exist, and every
|
|
225
|
+
// request arrives long after they all do.
|
|
226
|
+
const route = routeFor(req.path ?? req.url)
|
|
227
|
+
|
|
228
|
+
// Does that mount answer OPTIONS itself?
|
|
229
|
+
//
|
|
230
|
+
// The cors package defaults to preflightContinue: false, so it
|
|
231
|
+
// ENDS every OPTIONS in the process — 204, CORS headers, no
|
|
232
|
+
// next(). For a browser preflight that is exactly right, and it is
|
|
233
|
+
// what /mcp and /app want: their clients are browsers completing a
|
|
234
|
+
// Streamable HTTP handshake, which is why the mcp plugin pushes
|
|
235
|
+
// mcp-session-id into corsAllowHeaders.
|
|
236
|
+
//
|
|
237
|
+
// WebDAV is not that. The Microsoft WebDAV redirector decides
|
|
238
|
+
// whether a URL is a share at all from the `DAV:` header on
|
|
239
|
+
// OPTIONS — it is DISCOVERY, not a preflight. Answering it here
|
|
240
|
+
// returned 204 with no DAV header and five REST verbs, so Explorer
|
|
241
|
+
// reported ERROR_BAD_NET_NAME (0x80070043) and never even asked
|
|
242
|
+
// for credentials. Every other DAV client works, because Finder,
|
|
243
|
+
// curl, Cyberduck and gvfs do not gate on that header — so this
|
|
244
|
+
// read as "WebDAV is broken on Windows" with the mount logged
|
|
245
|
+
// happily and a 204 that looks like a normal preflight.
|
|
246
|
+
//
|
|
247
|
+
// nephele already computes the right answer and had it thrown
|
|
248
|
+
// away: its OPTIONS builds `DAV: 1, 3, 2` and an Allow list with
|
|
249
|
+
// PROPFIND, LOCK and the rest.
|
|
250
|
+
const ownsPreflight = Boolean(route?.methods?.includes('OPTIONS'))
|
|
251
|
+
|
|
252
|
+
// A route that wants no CORS headers at all.
|
|
253
|
+
//
|
|
254
|
+
// `origin: false` is the cors package's way of emitting none — the
|
|
255
|
+
// same thing --no-cors asks for globally, scoped to one mount.
|
|
256
|
+
//
|
|
257
|
+
// It needs no preflightContinue, and passing one here was dead
|
|
258
|
+
// code: with a falsy origin the package never builds an
|
|
259
|
+
// originCallback, so it calls next() and does not touch the
|
|
260
|
+
// response at all (cors/lib/index.js:210-228). The mount's own
|
|
261
|
+
// OPTIONS is therefore reached whatever preflightContinue says —
|
|
262
|
+
// which is why removing it changed no test, and why the comment
|
|
263
|
+
// that used to claim otherwise was wrong.
|
|
264
|
+
if (route?.cors === false) {
|
|
265
|
+
return callback(null, { origin: false })
|
|
266
|
+
}
|
|
267
|
+
|
|
220
268
|
callback(null, {
|
|
221
269
|
origin: configured === true ? '*' : String(configured),
|
|
222
|
-
|
|
270
|
+
// The verbs the mount actually serves, when it said. A fixed
|
|
271
|
+
// five advertised DELETE on a read-only mount and hid
|
|
272
|
+
// PROPFIND on a DAV one.
|
|
273
|
+
methods: route?.methods ?? ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
223
274
|
allowedHeaders: runtime.options.corsAllowHeaders,
|
|
224
275
|
exposedHeaders: runtime.options.corsExposeHeaders,
|
|
276
|
+
// Set the CORS headers, then hand the request on to the mount
|
|
277
|
+
// instead of ending it here.
|
|
278
|
+
preflightContinue: ownsPreflight,
|
|
225
279
|
})
|
|
226
280
|
}))
|
|
227
281
|
})
|