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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mikser-io",
3
- "version": "11.4.0",
3
+ "version": "11.5.0",
4
4
  "files": [
5
5
  "app.js",
6
6
  "index.js",
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
- methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
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
  })