mikser-io 11.4.0 → 11.5.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 +31 -0
- package/src/server.js +39 -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,14 @@ 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
|
+
// methods the HTTP verbs this mount actually serves. Two
|
|
91
|
+
// consequences, and the second one is load-bearing:
|
|
92
|
+
// CORS advertises these for the route instead of a
|
|
93
|
+
// fixed five, and listing OPTIONS declares that the
|
|
94
|
+
// mount answers OPTIONS ITSELF — so the global
|
|
95
|
+
// preflight steps aside rather than terminating it.
|
|
96
|
+
// Default null, meaning "ordinary REST verbs, and the
|
|
97
|
+
// preflight is CORS's to answer".
|
|
73
98
|
// label log prefix. Defaults to `plugin`.
|
|
74
99
|
// detail optional already-formatted log suffix, e.g.
|
|
75
100
|
// '(ops=[list,subscribe])'.
|
|
@@ -99,6 +124,7 @@ export function registerRoute({
|
|
|
99
124
|
plugin,
|
|
100
125
|
reachability = 'public',
|
|
101
126
|
streaming = false,
|
|
127
|
+
methods = null,
|
|
102
128
|
label,
|
|
103
129
|
detail,
|
|
104
130
|
displayPath,
|
|
@@ -112,7 +138,12 @@ export function registerRoute({
|
|
|
112
138
|
)
|
|
113
139
|
}
|
|
114
140
|
|
|
141
|
+
if (methods != null && (!Array.isArray(methods) || methods.some(m => typeof m !== 'string'))) {
|
|
142
|
+
throw new Error('registerRoute: `methods` must be an array of verb strings')
|
|
143
|
+
}
|
|
144
|
+
|
|
115
145
|
const descriptor = { path, plugin, reachability, streaming }
|
|
146
|
+
if (methods) descriptor.methods = methods.map(m => m.toUpperCase())
|
|
116
147
|
|
|
117
148
|
// Dedup by path — a re-register (same path) replaces rather than
|
|
118
149
|
// 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,48 @@ 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
|
+
|
|
220
252
|
callback(null, {
|
|
221
253
|
origin: configured === true ? '*' : String(configured),
|
|
222
|
-
|
|
254
|
+
// The verbs the mount actually serves, when it said. A fixed
|
|
255
|
+
// five advertised DELETE on a read-only mount and hid
|
|
256
|
+
// PROPFIND on a DAV one.
|
|
257
|
+
methods: route?.methods ?? ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
|
223
258
|
allowedHeaders: runtime.options.corsAllowHeaders,
|
|
224
259
|
exposedHeaders: runtime.options.corsExposeHeaders,
|
|
260
|
+
// Set the CORS headers, then hand the request on to the mount
|
|
261
|
+
// instead of ending it here.
|
|
262
|
+
preflightContinue: ownsPreflight,
|
|
225
263
|
})
|
|
226
264
|
}))
|
|
227
265
|
})
|