@timber-js/app 0.2.0-alpha.98 → 0.2.0-alpha.99
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/LICENSE +8 -0
- package/dist/_chunks/actions-CQ8Z8VGL.js +1061 -0
- package/dist/_chunks/actions-CQ8Z8VGL.js.map +1 -0
- package/dist/_chunks/build-output-helper-DXnW0qjz.js +61 -0
- package/dist/_chunks/build-output-helper-DXnW0qjz.js.map +1 -0
- package/dist/_chunks/{define-Itxvcd7F.js → define-B-Q_UMOD.js} +19 -23
- package/dist/_chunks/define-B-Q_UMOD.js.map +1 -0
- package/dist/_chunks/{define-C77ScO0m.js → define-CfBPoJb0.js} +24 -7
- package/dist/_chunks/define-CfBPoJb0.js.map +1 -0
- package/dist/_chunks/define-cookie-BjpIt4UC.js +194 -0
- package/dist/_chunks/define-cookie-BjpIt4UC.js.map +1 -0
- package/dist/_chunks/{format-CYBGxKtc.js → format-Bcn-Iv1x.js} +1 -1
- package/dist/_chunks/{format-CYBGxKtc.js.map → format-Bcn-Iv1x.js.map} +1 -1
- package/dist/_chunks/handler-store-B-lqaGyh.js +54 -0
- package/dist/_chunks/handler-store-B-lqaGyh.js.map +1 -0
- package/dist/_chunks/logger-0m8MsKdc.js +291 -0
- package/dist/_chunks/logger-0m8MsKdc.js.map +1 -0
- package/dist/_chunks/merge-search-params-BphMdht_.js +122 -0
- package/dist/_chunks/merge-search-params-BphMdht_.js.map +1 -0
- package/dist/_chunks/navigation-root-BCYczjml.js +96 -0
- package/dist/_chunks/navigation-root-BCYczjml.js.map +1 -0
- package/dist/_chunks/registry-I2ss-lvy.js +20 -0
- package/dist/_chunks/registry-I2ss-lvy.js.map +1 -0
- package/dist/_chunks/router-ref-h3-UaCQv.js +28 -0
- package/dist/_chunks/router-ref-h3-UaCQv.js.map +1 -0
- package/dist/_chunks/{schema-bridge-C3xl_vfb.js → schema-bridge-Cxu4l-7p.js} +1 -1
- package/dist/_chunks/{schema-bridge-C3xl_vfb.js.map → schema-bridge-Cxu4l-7p.js.map} +1 -1
- package/dist/_chunks/{segment-context-fHFLF1PE.js → segment-context-Dx_OizxD.js} +1 -1
- package/dist/_chunks/{segment-context-fHFLF1PE.js.map → segment-context-Dx_OizxD.js.map} +1 -1
- package/dist/_chunks/{router-ref-C8OCm7g7.js → ssr-data-B4CdH7rE.js} +2 -26
- package/dist/_chunks/ssr-data-B4CdH7rE.js.map +1 -0
- package/dist/_chunks/{stale-reload-BX5gL1r-.js → stale-reload-Bab885FO.js} +1 -1
- package/dist/_chunks/{stale-reload-BX5gL1r-.js.map → stale-reload-Bab885FO.js.map} +1 -1
- package/dist/_chunks/tracing-C8V-YGsP.js +329 -0
- package/dist/_chunks/tracing-C8V-YGsP.js.map +1 -0
- package/dist/_chunks/{use-query-states-BiV5GJgm.js → use-query-states-B2XTqxDR.js} +3 -19
- package/dist/_chunks/use-query-states-B2XTqxDR.js.map +1 -0
- package/dist/_chunks/{use-params-IOPu7E8t.js → use-segment-params-BkpKAQ7D.js} +9 -95
- package/dist/_chunks/use-segment-params-BkpKAQ7D.js.map +1 -0
- package/dist/_chunks/{walkers-VOXgavMF.js → walkers-Tg0Alwcg.js} +6 -3
- package/dist/_chunks/walkers-Tg0Alwcg.js.map +1 -0
- package/dist/_chunks/{dev-warnings-DpGRGoDi.js → warnings-Cg47l5sk.js} +3 -3
- package/dist/_chunks/warnings-Cg47l5sk.js.map +1 -0
- package/dist/adapters/build-output-helper.d.ts +28 -0
- package/dist/adapters/build-output-helper.d.ts.map +1 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -1
- package/dist/adapters/cloudflare.js +8 -28
- package/dist/adapters/cloudflare.js.map +1 -1
- package/dist/adapters/nitro.d.ts.map +1 -1
- package/dist/adapters/nitro.js +8 -26
- package/dist/adapters/nitro.js.map +1 -1
- package/dist/adapters/shared.d.ts +16 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/cache/index.js +9 -2
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/timber-cache.d.ts.map +1 -1
- package/dist/client/error-boundary.js +2 -1
- package/dist/client/error-boundary.js.map +1 -1
- package/dist/client/form.d.ts +10 -24
- package/dist/client/form.d.ts.map +1 -1
- package/dist/client/index.d.ts +1 -5
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +40 -90
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.d.ts +2 -1
- package/dist/client/internal.d.ts.map +1 -1
- package/dist/client/internal.js +81 -7
- package/dist/client/internal.js.map +1 -1
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/state.d.ts +1 -1
- package/dist/client/use-cookie.d.ts +8 -0
- package/dist/client/use-cookie.d.ts.map +1 -1
- package/dist/client/{use-params.d.ts → use-segment-params.d.ts} +1 -1
- package/dist/client/use-segment-params.d.ts.map +1 -0
- package/dist/codec.d.ts +1 -1
- package/dist/codec.d.ts.map +1 -1
- package/dist/codec.js +2 -2
- package/dist/config-types.d.ts +28 -0
- package/dist/config-types.d.ts.map +1 -1
- package/dist/cookies/define-cookie.d.ts +87 -35
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.d.ts +2 -1
- package/dist/cookies/index.d.ts.map +1 -1
- package/dist/cookies/index.js +48 -2
- package/dist/cookies/index.js.map +1 -0
- package/dist/cookies/json-cookie.d.ts +64 -0
- package/dist/cookies/json-cookie.d.ts.map +1 -0
- package/dist/cookies/validation.d.ts +46 -0
- package/dist/cookies/validation.d.ts.map +1 -0
- package/dist/{plugins/dev-404-page.d.ts → dev-tools/404-page.d.ts} +1 -1
- package/dist/dev-tools/404-page.d.ts.map +1 -0
- package/dist/{plugins/dev-browser-logs.d.ts → dev-tools/browser-logs.d.ts} +1 -1
- package/dist/dev-tools/browser-logs.d.ts.map +1 -0
- package/dist/{plugins/dev-error-page.d.ts → dev-tools/error-page.d.ts} +2 -2
- package/dist/dev-tools/error-page.d.ts.map +1 -0
- package/dist/{server/dev-holding-server.d.ts → dev-tools/holding-server.d.ts} +1 -1
- package/dist/dev-tools/holding-server.d.ts.map +1 -0
- package/dist/dev-tools/index.d.ts +31 -0
- package/dist/dev-tools/index.d.ts.map +1 -0
- package/dist/{server/dev-span-processor.d.ts → dev-tools/instrumentation.d.ts} +26 -6
- package/dist/dev-tools/instrumentation.d.ts.map +1 -0
- package/dist/{server/dev-logger.d.ts → dev-tools/logger.d.ts} +1 -1
- package/dist/dev-tools/logger.d.ts.map +1 -0
- package/dist/{plugins/dev-logs.d.ts → dev-tools/logs.d.ts} +1 -1
- package/dist/dev-tools/logs.d.ts.map +1 -0
- package/dist/{plugins/dev-error-overlay.d.ts → dev-tools/overlay.d.ts} +3 -12
- package/dist/dev-tools/overlay.d.ts.map +1 -0
- package/dist/dev-tools/stack-classifier.d.ts +34 -0
- package/dist/dev-tools/stack-classifier.d.ts.map +1 -0
- package/dist/{plugins/dev-terminal-error.d.ts → dev-tools/terminal.d.ts} +2 -2
- package/dist/dev-tools/terminal.d.ts.map +1 -0
- package/dist/{server/dev-warnings.d.ts → dev-tools/warnings.d.ts} +1 -1
- package/dist/dev-tools/warnings.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +97 -72
- package/dist/index.js.map +1 -1
- package/dist/plugin-context.d.ts +1 -1
- package/dist/plugin-context.d.ts.map +1 -1
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/routing/convention-lint.d.ts.map +1 -1
- package/dist/routing/index.js +1 -1
- package/dist/routing/scanner.d.ts.map +1 -1
- package/dist/routing/status-file-lint.d.ts.map +1 -1
- package/dist/search-params/define.d.ts +25 -7
- package/dist/search-params/define.d.ts.map +1 -1
- package/dist/search-params/index.js +5 -3
- package/dist/search-params/index.js.map +1 -1
- package/dist/search-params/wrappers.d.ts +2 -2
- package/dist/search-params/wrappers.d.ts.map +1 -1
- package/dist/segment-params/define.d.ts +23 -6
- package/dist/segment-params/define.d.ts.map +1 -1
- package/dist/segment-params/index.js +1 -1
- package/dist/server/access-gate.d.ts +4 -3
- package/dist/server/access-gate.d.ts.map +1 -1
- package/dist/server/action-handler.d.ts +15 -6
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +5 -5
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/asset-headers.d.ts +1 -15
- package/dist/server/asset-headers.d.ts.map +1 -1
- package/dist/server/cookie-context.d.ts +170 -0
- package/dist/server/cookie-context.d.ts.map +1 -0
- package/dist/server/cookie-parsing.d.ts +51 -0
- package/dist/server/cookie-parsing.d.ts.map +1 -0
- package/dist/server/deny-boundary.d.ts +90 -0
- package/dist/server/deny-boundary.d.ts.map +1 -0
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/early-hints-sender.d.ts.map +1 -1
- package/dist/server/index.d.ts +5 -4
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +4 -149
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.d.ts +6 -4
- package/dist/server/internal.d.ts.map +1 -1
- package/dist/server/internal.js +261 -408
- package/dist/server/internal.js.map +1 -1
- package/dist/server/logger.d.ts +14 -0
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/middleware-runner.d.ts +17 -0
- package/dist/server/middleware-runner.d.ts.map +1 -1
- package/dist/server/param-coercion.d.ts +26 -0
- package/dist/server/param-coercion.d.ts.map +1 -0
- package/dist/server/pipeline-helpers.d.ts +14 -7
- package/dist/server/pipeline-helpers.d.ts.map +1 -1
- package/dist/server/pipeline-outcome.d.ts +49 -0
- package/dist/server/pipeline-outcome.d.ts.map +1 -0
- package/dist/server/pipeline-phases.d.ts +4 -49
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +0 -2
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/request-context.d.ts +22 -159
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/rsc-entry/action-middleware-runner.d.ts +66 -0
- package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -0
- package/dist/server/rsc-entry/helpers.d.ts +1 -1
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts +50 -0
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -0
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts +59 -14
- package/dist/server/rsc-entry/wrap-action-dispatch.d.ts.map +1 -1
- package/dist/server/state-tree-diff.d.ts.map +1 -1
- package/dist/server/tracing.d.ts +1 -1
- package/dist/server/tracing.d.ts.map +1 -1
- package/dist/server/tree-builder.d.ts +45 -16
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/server/types.d.ts +48 -0
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/utils/escape-html.d.ts +14 -0
- package/dist/server/utils/escape-html.d.ts.map +1 -0
- package/dist/shims/headers.d.ts +2 -2
- package/dist/shims/headers.d.ts.map +1 -1
- package/dist/shims/navigation-client.d.ts +3 -1
- package/dist/shims/navigation-client.d.ts.map +1 -1
- package/dist/shims/navigation.d.ts +9 -4
- package/dist/shims/navigation.d.ts.map +1 -1
- package/package.json +6 -7
- package/src/adapters/build-output-helper.ts +77 -0
- package/src/adapters/cloudflare.ts +10 -50
- package/src/adapters/nitro.ts +11 -45
- package/src/adapters/shared.ts +40 -0
- package/src/cache/timber-cache.ts +3 -2
- package/src/cli.ts +0 -0
- package/src/client/form.tsx +17 -25
- package/src/client/index.ts +16 -9
- package/src/client/internal.ts +3 -2
- package/src/client/router.ts +1 -1
- package/src/client/rsc-fetch.ts +15 -0
- package/src/client/state.ts +2 -2
- package/src/client/use-cookie.ts +29 -0
- package/src/codec.ts +3 -7
- package/src/config-types.ts +28 -0
- package/src/cookies/define-cookie.ts +271 -78
- package/src/cookies/index.ts +11 -8
- package/src/cookies/json-cookie.ts +105 -0
- package/src/cookies/validation.ts +134 -0
- package/src/{plugins/dev-404-page.ts → dev-tools/404-page.ts} +2 -7
- package/src/{plugins/dev-error-page.ts → dev-tools/error-page.ts} +5 -32
- package/src/dev-tools/index.ts +90 -0
- package/src/dev-tools/instrumentation.ts +176 -0
- package/src/{plugins/dev-logs.ts → dev-tools/logs.ts} +2 -2
- package/src/{plugins/dev-error-overlay.ts → dev-tools/overlay.ts} +5 -23
- package/src/dev-tools/stack-classifier.ts +75 -0
- package/src/{plugins/dev-terminal-error.ts → dev-tools/terminal.ts} +4 -38
- package/src/{server/dev-warnings.ts → dev-tools/warnings.ts} +1 -1
- package/src/index.ts +11 -3
- package/src/plugin-context.ts +1 -1
- package/src/plugins/adapter-build.ts +3 -1
- package/src/plugins/dev-server.ts +3 -3
- package/src/plugins/shims.ts +1 -1
- package/src/plugins/static-build.ts +1 -1
- package/src/routing/convention-lint.ts +5 -4
- package/src/routing/scanner.ts +5 -2
- package/src/routing/status-file-lint.ts +4 -2
- package/src/search-params/define.ts +71 -15
- package/src/search-params/wrappers.ts +9 -2
- package/src/segment-params/define.ts +66 -13
- package/src/server/access-gate.tsx +9 -8
- package/src/server/action-handler.ts +28 -38
- package/src/server/als-registry.ts +5 -5
- package/src/server/asset-headers.ts +8 -34
- package/src/server/cookie-context.ts +468 -0
- package/src/server/cookie-parsing.ts +135 -0
- package/src/server/{deny-page-resolver.ts → deny-boundary.ts} +78 -14
- package/src/server/deny-renderer.ts +2 -7
- package/src/server/early-hints-sender.ts +3 -2
- package/src/server/fallback-error.ts +1 -1
- package/src/server/index.ts +13 -14
- package/src/server/internal.ts +10 -3
- package/src/server/logger.ts +23 -0
- package/src/server/middleware-runner.ts +44 -0
- package/src/server/param-coercion.ts +76 -0
- package/src/server/pipeline-helpers.ts +37 -13
- package/src/server/pipeline-outcome.ts +167 -0
- package/src/server/pipeline-phases.ts +27 -209
- package/src/server/pipeline.ts +2 -9
- package/src/server/request-context.ts +46 -451
- package/src/server/route-element-builder.ts +7 -3
- package/src/server/rsc-entry/action-middleware-runner.ts +167 -0
- package/src/server/rsc-entry/error-renderer.ts +1 -1
- package/src/server/rsc-entry/helpers.ts +2 -7
- package/src/server/rsc-entry/index.ts +34 -273
- package/src/server/rsc-entry/render-route.ts +304 -0
- package/src/server/rsc-entry/rsc-payload.ts +1 -1
- package/src/server/rsc-entry/ssr-renderer.ts +2 -2
- package/src/server/rsc-entry/wrap-action-dispatch.ts +316 -23
- package/src/server/ssr-entry.ts +1 -1
- package/src/server/state-tree-diff.ts +4 -1
- package/src/server/tracing.ts +3 -3
- package/src/server/tree-builder.ts +128 -52
- package/src/server/types.ts +52 -0
- package/src/server/utils/escape-html.ts +20 -0
- package/src/shims/headers.ts +3 -3
- package/src/shims/navigation-client.ts +4 -3
- package/src/shims/navigation.ts +9 -7
- package/dist/_chunks/actions-DLnUaR65.js +0 -421
- package/dist/_chunks/actions-DLnUaR65.js.map +0 -1
- package/dist/_chunks/als-registry-HS0LGUl2.js +0 -41
- package/dist/_chunks/als-registry-HS0LGUl2.js.map +0 -1
- package/dist/_chunks/debug-ECi_61pb.js +0 -108
- package/dist/_chunks/debug-ECi_61pb.js.map +0 -1
- package/dist/_chunks/define-C77ScO0m.js.map +0 -1
- package/dist/_chunks/define-Itxvcd7F.js.map +0 -1
- package/dist/_chunks/define-cookie-BowvzoP0.js +0 -94
- package/dist/_chunks/define-cookie-BowvzoP0.js.map +0 -1
- package/dist/_chunks/dev-warnings-DpGRGoDi.js.map +0 -1
- package/dist/_chunks/merge-search-params-Cm_KIWDX.js +0 -41
- package/dist/_chunks/merge-search-params-Cm_KIWDX.js.map +0 -1
- package/dist/_chunks/request-context-CK5tZqIP.js +0 -478
- package/dist/_chunks/request-context-CK5tZqIP.js.map +0 -1
- package/dist/_chunks/router-ref-C8OCm7g7.js.map +0 -1
- package/dist/_chunks/tracing-CCYbKn5n.js +0 -238
- package/dist/_chunks/tracing-CCYbKn5n.js.map +0 -1
- package/dist/_chunks/use-params-IOPu7E8t.js.map +0 -1
- package/dist/_chunks/use-query-states-BiV5GJgm.js.map +0 -1
- package/dist/_chunks/walkers-VOXgavMF.js.map +0 -1
- package/dist/client/use-params.d.ts.map +0 -1
- package/dist/plugins/dev-404-page.d.ts.map +0 -1
- package/dist/plugins/dev-browser-logs.d.ts.map +0 -1
- package/dist/plugins/dev-error-overlay.d.ts.map +0 -1
- package/dist/plugins/dev-error-page.d.ts.map +0 -1
- package/dist/plugins/dev-logs.d.ts.map +0 -1
- package/dist/plugins/dev-terminal-error.d.ts.map +0 -1
- package/dist/server/deny-page-resolver.d.ts +0 -52
- package/dist/server/deny-page-resolver.d.ts.map +0 -1
- package/dist/server/dev-fetch-instrumentation.d.ts +0 -22
- package/dist/server/dev-fetch-instrumentation.d.ts.map +0 -1
- package/dist/server/dev-holding-server.d.ts.map +0 -1
- package/dist/server/dev-logger.d.ts.map +0 -1
- package/dist/server/dev-span-processor.d.ts.map +0 -1
- package/dist/server/dev-warnings.d.ts.map +0 -1
- package/dist/server/page-deny-boundary.d.ts +0 -31
- package/dist/server/page-deny-boundary.d.ts.map +0 -1
- package/src/server/dev-fetch-instrumentation.ts +0 -96
- package/src/server/dev-span-processor.ts +0 -78
- package/src/server/page-deny-boundary.tsx +0 -56
- /package/src/client/{use-params.ts → use-segment-params.ts} +0 -0
- /package/src/{plugins/dev-browser-logs.ts → dev-tools/browser-logs.ts} +0 -0
- /package/src/{server/dev-holding-server.ts → dev-tools/holding-server.ts} +0 -0
- /package/src/{server/dev-logger.ts → dev-tools/logger.ts} +0 -0
|
@@ -3,28 +3,40 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Extracted from rsc-entry/index.ts so the wiring can be unit-tested in
|
|
5
5
|
* isolation from Vite's virtual modules. The wrapper is responsible for
|
|
6
|
-
*
|
|
6
|
+
* four things, in order:
|
|
7
7
|
*
|
|
8
8
|
* 1. **Pipeline-boundary CSRF validation** — runs on EVERY unsafe-method
|
|
9
9
|
* request, before any dispatch decision. This is the only line of
|
|
10
10
|
* defense for `route.ts` API handlers, which never see the action
|
|
11
11
|
* handler. See LOCAL-773.
|
|
12
12
|
*
|
|
13
|
-
* 2. **
|
|
13
|
+
* 2. **Middleware-on-actions execution** — when the request is a server
|
|
14
|
+
* action POST and `actions.runMiddleware !== false`, the matched
|
|
15
|
+
* route's `middleware.ts` chain runs BEFORE the action body. This
|
|
16
|
+
* closes the Next.js CVE-2025-29927 class of bug, where developers
|
|
17
|
+
* reasonably believe `middleware.ts` runs on every request and find
|
|
18
|
+
* out (the hard way) that actions silently bypass it. Middleware can
|
|
19
|
+
* short-circuit with a `Response`, `redirect()`, or `deny()`; can
|
|
20
|
+
* mutate cookies; and can inject request headers visible to the
|
|
21
|
+
* action body via `getHeaders()`. See TIM-871.
|
|
22
|
+
*
|
|
23
|
+
* 3. **Server action interception** — POST requests carrying an
|
|
14
24
|
* `x-rsc-action` header or React's `$ACTION_REF` form fields are
|
|
15
25
|
* handed to `handleActionRequest`, which executes the action and
|
|
16
26
|
* returns either an RSC response or a no-JS rerender signal.
|
|
17
27
|
*
|
|
18
|
-
*
|
|
28
|
+
* 4. **No-JS validation rerender** — when an action returns flash data
|
|
19
29
|
* instead of a redirect, the wrapper re-runs the page render via the
|
|
20
30
|
* pipeline with the post-action cookie state and `runWithFormFlash`
|
|
21
|
-
* so server components can read the flash.
|
|
31
|
+
* so server components can read the flash. The synthetic GET is
|
|
32
|
+
* marked via `markRequestBypassMiddleware` so the pipeline does not
|
|
33
|
+
* double-execute middleware on it. See TIM-836 / TIM-837 / TIM-871.
|
|
22
34
|
*
|
|
23
35
|
* Anything else falls through to `pipeline(req)` for normal route handling.
|
|
24
36
|
*
|
|
25
37
|
* The wrapper takes its dependencies as parameters (no module-level
|
|
26
38
|
* imports of virtual modules) so tests can construct it with stub
|
|
27
|
-
* pipelines and stub
|
|
39
|
+
* pipelines, stub revalidate renderers, and stub route matchers.
|
|
28
40
|
*/
|
|
29
41
|
|
|
30
42
|
import type { FormRerender } from '../action-handler.js';
|
|
@@ -32,8 +44,20 @@ import { handleActionRequest, isActionRequest } from '../action-handler.js';
|
|
|
32
44
|
import type { BodyLimitsConfig } from '../body-limits.js';
|
|
33
45
|
import { validateCsrf, type CsrfConfig } from '../csrf.js';
|
|
34
46
|
import { runWithFormFlash } from '../form-flash.js';
|
|
47
|
+
import { seedRequestCookies } from '../cookie-context.js';
|
|
48
|
+
import { markRequestBypassMiddleware } from '../middleware-runner.js';
|
|
49
|
+
import { DenySignal } from '../primitives.js';
|
|
50
|
+
import { canonicalize } from '../canonicalize.js';
|
|
51
|
+
import {
|
|
52
|
+
buildRedirectResponse,
|
|
53
|
+
cloneWithMutableHeaders,
|
|
54
|
+
fireOnRequestError,
|
|
55
|
+
} from '../pipeline-helpers.js';
|
|
56
|
+
import { logRenderError } from '../logger.js';
|
|
57
|
+
import type { RouteMatch, RouteMatcher } from '../pipeline.js';
|
|
35
58
|
import type { SensitiveFieldsOption } from '../sensitive-fields.js';
|
|
36
59
|
import type { RevalidateRenderer } from '../actions.js';
|
|
60
|
+
import { runMiddlewareForAction, type CoerceSegmentParamsFn } from './action-middleware-runner.js';
|
|
37
61
|
|
|
38
62
|
// ─── Types ────────────────────────────────────────────────────────────────
|
|
39
63
|
|
|
@@ -48,6 +72,14 @@ import type { RevalidateRenderer } from '../actions.js';
|
|
|
48
72
|
*/
|
|
49
73
|
export type RevalidateRendererFactory = (req: Request) => RevalidateRenderer;
|
|
50
74
|
|
|
75
|
+
/** Optional renderer for fallback deny pages — mirrors `PipelineConfig.renderDenyFallback`. */
|
|
76
|
+
export type RenderDenyFallbackFn = (
|
|
77
|
+
deny: DenySignal,
|
|
78
|
+
req: Request,
|
|
79
|
+
responseHeaders: Headers,
|
|
80
|
+
match?: RouteMatch
|
|
81
|
+
) => Response | Promise<Response>;
|
|
82
|
+
|
|
51
83
|
/** Dependencies for the action-dispatch wrapper. */
|
|
52
84
|
export interface ActionDispatchDeps {
|
|
53
85
|
/** CSRF configuration (Origin allow-list, on/off switch). */
|
|
@@ -58,6 +90,42 @@ export interface ActionDispatchDeps {
|
|
|
58
90
|
sensitiveFields?: SensitiveFieldsOption;
|
|
59
91
|
/** Per-request factory that builds a `RevalidateRenderer`. */
|
|
60
92
|
buildRevalidateRenderer: RevalidateRendererFactory;
|
|
93
|
+
/**
|
|
94
|
+
* Route matcher — when present, enables middleware-on-actions execution.
|
|
95
|
+
* Mirrors `PipelineConfig.matchRoute`. Tests that don't exercise the
|
|
96
|
+
* middleware path may omit this; the wrapper falls back to the legacy
|
|
97
|
+
* "actions skip middleware" behavior.
|
|
98
|
+
*/
|
|
99
|
+
matchRoute?: RouteMatcher;
|
|
100
|
+
/**
|
|
101
|
+
* Segment-param coercer — runs the matched route's `params.ts` codecs
|
|
102
|
+
* so the middleware context sees typed `segmentParams`. Required when
|
|
103
|
+
* `matchRoute` is provided.
|
|
104
|
+
*/
|
|
105
|
+
coerceSegmentParams?: CoerceSegmentParamsFn;
|
|
106
|
+
/**
|
|
107
|
+
* Renderer for fallback deny pages — used when middleware throws a
|
|
108
|
+
* `DenySignal` and the framework wants to render `403.tsx` / `404.tsx`
|
|
109
|
+
* instead of returning a bare empty Response. Optional — when omitted
|
|
110
|
+
* the wrapper falls back to a bare status response.
|
|
111
|
+
*/
|
|
112
|
+
renderDenyFallback?: RenderDenyFallbackFn;
|
|
113
|
+
/**
|
|
114
|
+
* Whether to run `middleware.ts` on server action requests. Defaults
|
|
115
|
+
* to `true` (the safe default). Controlled by
|
|
116
|
+
* `actions.runMiddleware` in `timber.config.ts`. See TIM-871.
|
|
117
|
+
*/
|
|
118
|
+
runMiddleware?: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Whether to strip trailing slashes during pathname canonicalization
|
|
121
|
+
* for route matching. Mirrors `PipelineConfig.stripTrailingSlash`;
|
|
122
|
+
* defaults to `true` when omitted, matching the page pipeline. The
|
|
123
|
+
* wrapper MUST canonicalize before matching so non-canonical action
|
|
124
|
+
* POSTs (`/admin/`, `/admin//`, encoded separators) cannot bypass the
|
|
125
|
+
* middleware gate by missing the route match and falling through to
|
|
126
|
+
* the legacy path. See TIM-871 / codex review.
|
|
127
|
+
*/
|
|
128
|
+
stripTrailingSlash?: boolean;
|
|
61
129
|
}
|
|
62
130
|
|
|
63
131
|
// ─── Implementation ───────────────────────────────────────────────────────
|
|
@@ -66,15 +134,7 @@ export interface ActionDispatchDeps {
|
|
|
66
134
|
* Wrap a pipeline function with CSRF validation and server-action dispatch.
|
|
67
135
|
*
|
|
68
136
|
* The returned handler is the framework's outermost request entry point.
|
|
69
|
-
* Its responsibilities
|
|
70
|
-
*
|
|
71
|
-
* 1. CSRF (Origin/Host) validation on every unsafe-method request.
|
|
72
|
-
* Safe methods (GET/HEAD/OPTIONS) short-circuit inside `validateCsrf`,
|
|
73
|
-
* so this is a no-op for reads.
|
|
74
|
-
* 2. Server-action interception for POSTs that match `isActionRequest`.
|
|
75
|
-
* 3. No-JS validation rerender when an action returns a `FormRerender`
|
|
76
|
-
* signal instead of a redirect.
|
|
77
|
-
* 4. Otherwise, delegate to `pipeline(req)`.
|
|
137
|
+
* Its responsibilities are documented in the file header.
|
|
78
138
|
*
|
|
79
139
|
* The duplicate `validateCsrf` call inside `handleActionRequest` is left in
|
|
80
140
|
* place as defense-in-depth (no-op on the happy path) so the action handler
|
|
@@ -85,6 +145,9 @@ export function wrapPipelineWithActionDispatch(
|
|
|
85
145
|
pipeline: (req: Request) => Promise<Response>,
|
|
86
146
|
deps: ActionDispatchDeps
|
|
87
147
|
): (req: Request) => Promise<Response> {
|
|
148
|
+
const middlewareEnabled = deps.runMiddleware !== false;
|
|
149
|
+
const stripTrailingSlash = deps.stripTrailingSlash ?? true;
|
|
150
|
+
|
|
88
151
|
return async (req: Request): Promise<Response> => {
|
|
89
152
|
// ─── 1. Pipeline-boundary CSRF validation (LOCAL-773) ─────────────
|
|
90
153
|
//
|
|
@@ -104,7 +167,192 @@ export function wrapPipelineWithActionDispatch(
|
|
|
104
167
|
|
|
105
168
|
// ─── 2. Server action interception ────────────────────────────────
|
|
106
169
|
if (isActionRequest(req)) {
|
|
107
|
-
|
|
170
|
+
// ─── 2a. Route-type check + canonicalize (TIM-870) ──────────────
|
|
171
|
+
//
|
|
172
|
+
// Canonicalize and match the route BEFORE any body parsing. If the
|
|
173
|
+
// matched route is an API route (route.ts), skip action detection
|
|
174
|
+
// entirely and dispatch straight to the pipeline. Server actions
|
|
175
|
+
// only live on page routes; POSTs to route.ts are API requests
|
|
176
|
+
// whose body must reach the handler untouched — not pre-parsed
|
|
177
|
+
// looking for $ACTION_REF fields.
|
|
178
|
+
//
|
|
179
|
+
// This also avoids allocating a full formData() parse over large
|
|
180
|
+
// multipart uploads (e.g. 50 MB file uploads to /api/upload) that
|
|
181
|
+
// would otherwise be buffered and discarded by handleFormAction.
|
|
182
|
+
//
|
|
183
|
+
// The same canonicalized match is reused for the middleware-on-
|
|
184
|
+
// actions path below, avoiding a redundant match.
|
|
185
|
+
let match: RouteMatch | null = null;
|
|
186
|
+
|
|
187
|
+
if (deps.matchRoute) {
|
|
188
|
+
// Canonicalize the pathname BEFORE matching, with the exact same
|
|
189
|
+
// rules the page pipeline uses (`canonicalize()` in
|
|
190
|
+
// `pipeline-phases.ts` stage 1). Without this, an attacker could
|
|
191
|
+
// POST to a non-canonical variant of an authenticated route —
|
|
192
|
+
// `/admin/` with a trailing slash, `/admin//` with a doubled
|
|
193
|
+
// slash, `/adm%69n` with percent-escapes, `/admin%2fnested` with
|
|
194
|
+
// an encoded separator — fail the match here, fall through to
|
|
195
|
+
// the legacy "no middleware" path, and still reach the action
|
|
196
|
+
// handler. The canonicalization step closes that bypass and
|
|
197
|
+
// guarantees the wrapper matches EXACTLY the same route the page
|
|
198
|
+
// pipeline would match. A canonicalize failure (encoded
|
|
199
|
+
// separator, null byte, malformed escape, `..` escaping root)
|
|
200
|
+
// returns the canonicalizer's status directly — the request is
|
|
201
|
+
// malformed, never dispatched. See TIM-871 (codex review).
|
|
202
|
+
const url = new URL(req.url);
|
|
203
|
+
const canonical = canonicalize(url.pathname, stripTrailingSlash);
|
|
204
|
+
if (!canonical.ok) {
|
|
205
|
+
return new Response(null, { status: canonical.status });
|
|
206
|
+
}
|
|
207
|
+
match = deps.matchRoute(canonical.pathname);
|
|
208
|
+
|
|
209
|
+
// Skip action detection for route.ts API handlers (TIM-870).
|
|
210
|
+
// Server actions only target page routes. A POST to a route.ts
|
|
211
|
+
// path is an API request — the full body should reach the route
|
|
212
|
+
// handler without being pre-parsed by handleFormAction.
|
|
213
|
+
if (match) {
|
|
214
|
+
const leaf = match.segments[match.segments.length - 1];
|
|
215
|
+
if (leaf?.route) {
|
|
216
|
+
return pipeline(req);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ─── 2b. Middleware-on-actions (TIM-871) ────────────────────────
|
|
222
|
+
//
|
|
223
|
+
// When middleware execution is enabled and the request matches a
|
|
224
|
+
// known route, run the matched chain BEFORE the action handler.
|
|
225
|
+
// The middleware run happens inside its own `runWithRequestContext`
|
|
226
|
+
// scope so middleware can read/write cookies and inject request
|
|
227
|
+
// headers via the standard ALS APIs. Mutations are captured at the
|
|
228
|
+
// end of the scope and threaded into the action handler's own ALS
|
|
229
|
+
// scope via `seedRequestCookies` + a request rebuilt with overlay
|
|
230
|
+
// headers merged in.
|
|
231
|
+
let downstreamReq = req;
|
|
232
|
+
let middlewareSetCookieHeaders: string[] = [];
|
|
233
|
+
|
|
234
|
+
if (
|
|
235
|
+
middlewareEnabled &&
|
|
236
|
+
deps.coerceSegmentParams &&
|
|
237
|
+
match &&
|
|
238
|
+
match.middlewareChain.length > 0
|
|
239
|
+
) {
|
|
240
|
+
const outcome = await runMiddlewareForAction(req, match, deps.coerceSegmentParams);
|
|
241
|
+
|
|
242
|
+
// ─── Translate middleware outcome ─────────────────────────
|
|
243
|
+
if (outcome.kind === 'param-coercion-error') {
|
|
244
|
+
// Bad segment params → 404. Matches the page pipeline.
|
|
245
|
+
return new Response(null, { status: 404 });
|
|
246
|
+
}
|
|
247
|
+
if (outcome.kind === 'error') {
|
|
248
|
+
// Unhandled middleware error → 500.
|
|
249
|
+
return new Response(null, { status: 500 });
|
|
250
|
+
}
|
|
251
|
+
if (outcome.kind === 'short-circuit') {
|
|
252
|
+
// Middleware returned a Response. Apply its Set-Cookie
|
|
253
|
+
// snapshot before returning. Clone unconditionally so the
|
|
254
|
+
// header bag is mutable.
|
|
255
|
+
const finalResponse = cloneWithMutableHeaders(outcome.response);
|
|
256
|
+
for (const value of outcome.setCookieHeaders) {
|
|
257
|
+
finalResponse.headers.append('Set-Cookie', value);
|
|
258
|
+
}
|
|
259
|
+
return finalResponse;
|
|
260
|
+
}
|
|
261
|
+
if (outcome.kind === 'redirect') {
|
|
262
|
+
// RedirectSignal from middleware → standard redirect Response.
|
|
263
|
+
// Use `buildRedirectResponse` so the with-JS path produces a
|
|
264
|
+
// 204 + X-Timber-Redirect (SPA navigation) and the no-JS path
|
|
265
|
+
// produces a real HTTP 30x — exactly the same translation
|
|
266
|
+
// the page pipeline applies in `outcomeToResponse`.
|
|
267
|
+
const headers = new Headers();
|
|
268
|
+
for (const value of outcome.setCookieHeaders) {
|
|
269
|
+
headers.append('Set-Cookie', value);
|
|
270
|
+
}
|
|
271
|
+
return buildRedirectResponse(outcome.signal, req, headers);
|
|
272
|
+
}
|
|
273
|
+
if (outcome.kind === 'deny') {
|
|
274
|
+
// DenySignal from middleware → render the matched route's
|
|
275
|
+
// colocated deny page if available, otherwise a bare empty
|
|
276
|
+
// status response. Mirrors the page pipeline's deny handling.
|
|
277
|
+
const headers = new Headers();
|
|
278
|
+
for (const value of outcome.setCookieHeaders) {
|
|
279
|
+
headers.append('Set-Cookie', value);
|
|
280
|
+
}
|
|
281
|
+
if (deps.renderDenyFallback) {
|
|
282
|
+
try {
|
|
283
|
+
return cloneWithMutableHeaders(
|
|
284
|
+
await deps.renderDenyFallback(outcome.signal, req, headers, outcome.match)
|
|
285
|
+
);
|
|
286
|
+
} catch (denyRenderError) {
|
|
287
|
+
// Deny page rendering failed — log before falling through to bare
|
|
288
|
+
// response. Without this, a crashing deny page on the action path
|
|
289
|
+
// produces a blank response with zero server-side signal. See TIM-876.
|
|
290
|
+
const url = new URL(req.url);
|
|
291
|
+
logRenderError({ method: req.method, path: url.pathname, error: denyRenderError });
|
|
292
|
+
await fireOnRequestError(denyRenderError, req, 'render');
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return new Response(null, { status: outcome.signal.status, headers });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ─── Continue: middleware passed ──────────────────────────
|
|
299
|
+
//
|
|
300
|
+
// Build a downstream Request that the action handler will see:
|
|
301
|
+
// - Original headers + middleware's request-header overlay
|
|
302
|
+
// merged on top, so `getHeaders()` inside the action body
|
|
303
|
+
// observes the injected values.
|
|
304
|
+
// - Cookie header dropped; the post-middleware RYW cookie
|
|
305
|
+
// state is threaded directly into the action handler's
|
|
306
|
+
// request context via `seedRequestCookies`. This avoids
|
|
307
|
+
// a Cookie-header round-trip that would re-parse via
|
|
308
|
+
// `parseCookieHeader` — the same H-3 smuggling primitive
|
|
309
|
+
// mitigated by TIM-868. The action's own cookie reads
|
|
310
|
+
// therefore observe both the original cookies and any
|
|
311
|
+
// middleware mutations, with no cleartext encoding step
|
|
312
|
+
// in between.
|
|
313
|
+
// - Body forwarded as-is so the action handler can decode it.
|
|
314
|
+
// - Method preserved (POST).
|
|
315
|
+
//
|
|
316
|
+
// We hold the middleware Set-Cookie snapshot to prepend to the
|
|
317
|
+
// final response below — middleware writes precede action
|
|
318
|
+
// writes in the response order so the browser's last-wins
|
|
319
|
+
// resolution lets the action override middleware on conflict.
|
|
320
|
+
const mergedHeaders = new Headers(req.headers);
|
|
321
|
+
outcome.overlay.forEach((value, key) => {
|
|
322
|
+
mergedHeaders.set(key, value);
|
|
323
|
+
});
|
|
324
|
+
mergedHeaders.delete('cookie');
|
|
325
|
+
downstreamReq = new Request(req.url, {
|
|
326
|
+
method: req.method,
|
|
327
|
+
headers: mergedHeaders,
|
|
328
|
+
body: req.body,
|
|
329
|
+
// Required by undici when constructing a Request with a
|
|
330
|
+
// streaming body — `req.body` is a ReadableStream and the
|
|
331
|
+
// fetch spec needs an explicit half-duplex declaration.
|
|
332
|
+
// @ts-expect-error — `duplex` is not in the standard Request init type yet.
|
|
333
|
+
duplex: 'half',
|
|
334
|
+
});
|
|
335
|
+
seedRequestCookies(downstreamReq, outcome.cookies);
|
|
336
|
+
middlewareSetCookieHeaders = outcome.setCookieHeaders;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ─── 2c. Action handler dispatch ────────────────────────────────
|
|
340
|
+
//
|
|
341
|
+
// The revalidate renderer is built from the ORIGINAL request, not
|
|
342
|
+
// `downstreamReq`. The downstream request had its `cookie` header
|
|
343
|
+
// stripped (the post-middleware RYW cookie state is threaded
|
|
344
|
+
// through ALS via `seedRequestCookies` instead, to preserve the
|
|
345
|
+
// H-3 smuggling invariant from TIM-868). But the revalidate
|
|
346
|
+
// renderer in `rsc-entry/index.ts` forwards `req.headers` onto the
|
|
347
|
+
// synthetic revalidation Request it builds for the target path —
|
|
348
|
+
// if we passed `downstreamReq` here, that synthetic request would
|
|
349
|
+
// carry no cookies, and an action that calls `revalidatePath()`
|
|
350
|
+
// would re-render the target route with no session / tenant / auth
|
|
351
|
+
// context. Using `req` (the unmodified inbound request) preserves
|
|
352
|
+
// the original cookie header for the revalidation side channel
|
|
353
|
+
// while `seedRequestCookies` handles the middleware RYW state for
|
|
354
|
+
// the action body itself. See TIM-871 (codex review).
|
|
355
|
+
const actionResponse = await handleActionRequest(downstreamReq, {
|
|
108
356
|
csrf: deps.csrfConfig,
|
|
109
357
|
bodyLimits: { limits: deps.bodyLimits },
|
|
110
358
|
sensitiveFields: deps.sensitiveFields,
|
|
@@ -117,22 +365,36 @@ export function wrapPipelineWithActionDispatch(
|
|
|
117
365
|
const formRerender = actionResponse as FormRerender;
|
|
118
366
|
// Build a synthetic GET request for the rerender pipeline:
|
|
119
367
|
// - Same URL (so route matching lands on the same page)
|
|
120
|
-
// - Cookie header
|
|
121
|
-
//
|
|
368
|
+
// - Cookie header DROPPED entirely. The post-action RYW state
|
|
369
|
+
// is threaded into the rerender request context as a parsed
|
|
370
|
+
// `Map<string, string>` via `seedRequestCookies` below, so
|
|
371
|
+
// `parseCookieHeader` is never called for this request.
|
|
372
|
+
// This eliminates the value-smuggling primitive that the
|
|
373
|
+
// previous string round-trip exposed: a `;`-laden cookie
|
|
374
|
+
// value would otherwise split into sibling cookies during
|
|
375
|
+
// re-parse and let an attacker inject `role=admin` /
|
|
376
|
+
// forged session cookies into the rerender response. See
|
|
377
|
+
// ONGOING_SECURITY.md H-3 (TIM-868) and TIM-837.
|
|
122
378
|
// - Method GET because the rerender is conceptually a page
|
|
123
379
|
// render, not a re-POST. The pipeline doesn't branch on
|
|
124
380
|
// method for page rendering, and constructing a POST without
|
|
125
381
|
// a body is awkward across Request implementations.
|
|
382
|
+
// - Marked via `markRequestBypassMiddleware` so the pipeline
|
|
383
|
+
// skips its middleware phase on this synthetic request.
|
|
384
|
+
// Middleware already ran once on the inbound POST (above);
|
|
385
|
+
// letting the pipeline run it again would double-execute
|
|
386
|
+
// auth, rate limiting, and request-header injection. See
|
|
387
|
+
// TIM-871.
|
|
126
388
|
const rerenderHeaders = new Headers(req.headers);
|
|
127
|
-
|
|
128
|
-
rerenderHeaders.set('cookie', formRerender.cookieHeader);
|
|
129
|
-
} else {
|
|
130
|
-
rerenderHeaders.delete('cookie');
|
|
131
|
-
}
|
|
389
|
+
rerenderHeaders.delete('cookie');
|
|
132
390
|
const rerenderReq = new Request(req.url, {
|
|
133
391
|
method: 'GET',
|
|
134
392
|
headers: rerenderHeaders,
|
|
135
393
|
});
|
|
394
|
+
// Seed BEFORE pipeline() runs — runWithRequestContext consumes
|
|
395
|
+
// the seed when it constructs the per-request store.
|
|
396
|
+
seedRequestCookies(rerenderReq, formRerender.cookies);
|
|
397
|
+
markRequestBypassMiddleware(rerenderReq);
|
|
136
398
|
const response = await runWithFormFlash(formRerender.rerender, () =>
|
|
137
399
|
pipeline(rerenderReq)
|
|
138
400
|
);
|
|
@@ -140,12 +402,43 @@ export function wrapPipelineWithActionDispatch(
|
|
|
140
402
|
// The pipeline above runs in its own request context with a fresh
|
|
141
403
|
// cookie jar, so cookies set inside the action would otherwise be
|
|
142
404
|
// silently dropped on the no-JS rerender path. See TIM-836
|
|
143
|
-
// (LOCAL-740).
|
|
405
|
+
// (LOCAL-740). Middleware-set cookies are prepended first so
|
|
406
|
+
// browser last-wins still lets action writes override.
|
|
407
|
+
for (const value of middlewareSetCookieHeaders) {
|
|
408
|
+
response.headers.append('Set-Cookie', value);
|
|
409
|
+
}
|
|
144
410
|
for (const value of formRerender.setCookieHeaders) {
|
|
145
411
|
response.headers.append('Set-Cookie', value);
|
|
146
412
|
}
|
|
147
413
|
return response;
|
|
148
414
|
}
|
|
415
|
+
// Apply middleware Set-Cookie snapshot to the action's RSC
|
|
416
|
+
// response. Action's own cookies were already appended inside
|
|
417
|
+
// `handleActionRequest` via `getSetCookieHeaders()` before the
|
|
418
|
+
// action ALS scope exited; we prepend middleware writes so
|
|
419
|
+
// browser last-wins lets action writes take precedence on
|
|
420
|
+
// conflicting names.
|
|
421
|
+
if (middlewareSetCookieHeaders.length > 0) {
|
|
422
|
+
// Middleware writes go FIRST in the response order, so we
|
|
423
|
+
// build a fresh Headers and rebuild the Response. Cloning the
|
|
424
|
+
// Response keeps the body stream intact.
|
|
425
|
+
const mergedHeaders = new Headers();
|
|
426
|
+
for (const value of middlewareSetCookieHeaders) {
|
|
427
|
+
mergedHeaders.append('Set-Cookie', value);
|
|
428
|
+
}
|
|
429
|
+
actionResponse.headers.forEach((value, key) => {
|
|
430
|
+
if (key.toLowerCase() === 'set-cookie') {
|
|
431
|
+
mergedHeaders.append('Set-Cookie', value);
|
|
432
|
+
} else {
|
|
433
|
+
mergedHeaders.set(key, value);
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
return new Response(actionResponse.body, {
|
|
437
|
+
status: actionResponse.status,
|
|
438
|
+
statusText: actionResponse.statusText,
|
|
439
|
+
headers: mergedHeaders,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
149
442
|
return actionResponse;
|
|
150
443
|
}
|
|
151
444
|
}
|
package/src/server/ssr-entry.ts
CHANGED
|
@@ -37,7 +37,7 @@ import { SsrStreamError } from './primitives.js';
|
|
|
37
37
|
import { createBufferedTransformStream, injectHead, injectRscPayload } from './html-injectors.js';
|
|
38
38
|
import { wrapSsrElement } from './ssr-wrappers.js';
|
|
39
39
|
import { withSpan } from './tracing.js';
|
|
40
|
-
import { setCurrentParams } from '../client/use-params.js';
|
|
40
|
+
import { setCurrentParams } from '../client/use-segment-params.js';
|
|
41
41
|
import { registerSsrDataProvider, type SsrData } from '../client/ssr-data.js';
|
|
42
42
|
|
|
43
43
|
// Pre-import Node.js stream modules at module load time — not per-request.
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* See design/13-security.md §"State tree manipulation"
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { swallow } from './logger.js';
|
|
18
|
+
|
|
17
19
|
/**
|
|
18
20
|
* Parse the X-Timber-State-Tree header from a request.
|
|
19
21
|
*
|
|
@@ -33,7 +35,8 @@ export function parseClientStateTree(req: Request): Set<string> | null {
|
|
|
33
35
|
return null;
|
|
34
36
|
}
|
|
35
37
|
return new Set(parsed.segments as string[]);
|
|
36
|
-
} catch {
|
|
38
|
+
} catch (err) {
|
|
39
|
+
swallow(err, 'malformed X-Timber-State-Tree header');
|
|
37
40
|
return null;
|
|
38
41
|
}
|
|
39
42
|
}
|
package/src/server/tracing.ts
CHANGED
|
@@ -105,17 +105,17 @@ export function getTraceStore(): TraceStore | undefined {
|
|
|
105
105
|
* Only called in dev mode — zero overhead in production.
|
|
106
106
|
*/
|
|
107
107
|
export async function initDevTracing(
|
|
108
|
-
config: import('
|
|
108
|
+
config: import('../dev-tools/logger.js').DevLoggerConfig
|
|
109
109
|
): Promise<void> {
|
|
110
110
|
const api = await getOtelApi();
|
|
111
111
|
if (!api) return;
|
|
112
112
|
|
|
113
|
-
let DevSpanProcessor: typeof import('
|
|
113
|
+
let DevSpanProcessor: typeof import('../dev-tools/instrumentation.js').DevSpanProcessor;
|
|
114
114
|
let BasicTracerProvider: typeof import('@opentelemetry/sdk-trace-base').BasicTracerProvider;
|
|
115
115
|
let AsyncLocalStorageContextManager: typeof import('@opentelemetry/context-async-hooks').AsyncLocalStorageContextManager;
|
|
116
116
|
|
|
117
117
|
try {
|
|
118
|
-
({ DevSpanProcessor } = await import('
|
|
118
|
+
({ DevSpanProcessor } = await import('../dev-tools/instrumentation.js'));
|
|
119
119
|
({ BasicTracerProvider } = await import('@opentelemetry/sdk-trace-base'));
|
|
120
120
|
({ AsyncLocalStorageContextManager } = await import('@opentelemetry/context-async-hooks'));
|
|
121
121
|
} catch (err) {
|