react-router 7.11.0-pre.0 → 7.12.0-pre.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.
Files changed (68) hide show
  1. package/CHANGELOG.md +52 -2
  2. package/dist/development/{browser-Cv4JZyZ5.d.mts → browser-BEPxnEBW.d.mts} +4 -2
  3. package/dist/{production/browser-Cv4JZyZ5.d.mts → development/browser-CJ9_du-U.d.ts} +4 -2
  4. package/dist/development/{chunk-QMKP6CC3.mjs → chunk-2RQJSHF5.mjs} +111 -11
  5. package/dist/development/{chunk-JKMHOZYW.js → chunk-AUGQXA25.js} +98 -108
  6. package/dist/development/{chunk-OVG6YSZ5.js → chunk-JBLDGBX7.js} +7 -7
  7. package/dist/development/{chunk-KRMLYMWA.mjs → chunk-KYKH2NCS.mjs} +122 -50
  8. package/dist/development/{chunk-UO7KGW2U.js → chunk-MNTWZBMV.js} +117 -35
  9. package/dist/development/dom-export.d.mts +2 -2
  10. package/dist/development/dom-export.d.ts +2 -2
  11. package/dist/development/dom-export.js +35 -30
  12. package/dist/development/dom-export.mjs +12 -7
  13. package/dist/development/{index-react-server-client-P7VgYu6T.d.mts → index-react-server-client-IoJGLOqV.d.mts} +3 -1
  14. package/dist/{production/index-react-server-client-Cv5Q9lf0.d.ts → development/index-react-server-client-gGyf-7Xp.d.ts} +3 -1
  15. package/dist/development/index-react-server-client.d.mts +2 -2
  16. package/dist/development/index-react-server-client.d.ts +2 -2
  17. package/dist/development/index-react-server-client.js +4 -4
  18. package/dist/development/index-react-server-client.mjs +2 -2
  19. package/dist/development/index-react-server.d.mts +3 -1
  20. package/dist/development/index-react-server.d.ts +3 -1
  21. package/dist/development/index-react-server.js +92 -7
  22. package/dist/development/index-react-server.mjs +92 -7
  23. package/dist/development/index.d.mts +9 -9
  24. package/dist/development/index.d.ts +9 -9
  25. package/dist/development/index.js +205 -101
  26. package/dist/development/index.mjs +7 -3
  27. package/dist/{production/instrumentation-BlrVzjbg.d.ts → development/instrumentation-DvHY1sgY.d.ts} +45 -1
  28. package/dist/development/lib/types/internal.d.mts +2 -2
  29. package/dist/development/lib/types/internal.d.ts +2 -2
  30. package/dist/development/lib/types/internal.js +1 -1
  31. package/dist/development/lib/types/internal.mjs +1 -1
  32. package/dist/development/{register-BGQUMCK4.d.ts → register-Bm80E9qL.d.ts} +1 -1
  33. package/dist/development/{register-DTJJbt1o.d.mts → register-CS_tiXsm.d.mts} +1 -1
  34. package/dist/development/{router-5fbeEIMQ.d.mts → router-5iOvts3c.d.mts} +45 -1
  35. package/dist/{development/browser-o-qhcuhA.d.ts → production/browser-BEPxnEBW.d.mts} +4 -2
  36. package/dist/production/{browser-o-qhcuhA.d.ts → browser-CJ9_du-U.d.ts} +4 -2
  37. package/dist/production/{chunk-IDHO4Q57.mjs → chunk-HFQUWXEK.mjs} +122 -50
  38. package/dist/production/{chunk-J4JITZ76.mjs → chunk-ILRYJQTC.mjs} +111 -11
  39. package/dist/production/{chunk-YGB3JEIP.js → chunk-O6YLM5NB.js} +7 -7
  40. package/dist/production/{chunk-M5W3Q3T5.js → chunk-QWJQISZK.js} +98 -108
  41. package/dist/production/{chunk-AO22ZXHI.js → chunk-ZR2NIBH2.js} +117 -35
  42. package/dist/production/dom-export.d.mts +2 -2
  43. package/dist/production/dom-export.d.ts +2 -2
  44. package/dist/production/dom-export.js +35 -30
  45. package/dist/production/dom-export.mjs +12 -7
  46. package/dist/production/{index-react-server-client-P7VgYu6T.d.mts → index-react-server-client-IoJGLOqV.d.mts} +3 -1
  47. package/dist/{development/index-react-server-client-Cv5Q9lf0.d.ts → production/index-react-server-client-gGyf-7Xp.d.ts} +3 -1
  48. package/dist/production/index-react-server-client.d.mts +2 -2
  49. package/dist/production/index-react-server-client.d.ts +2 -2
  50. package/dist/production/index-react-server-client.js +4 -4
  51. package/dist/production/index-react-server-client.mjs +2 -2
  52. package/dist/production/index-react-server.d.mts +3 -1
  53. package/dist/production/index-react-server.d.ts +3 -1
  54. package/dist/production/index-react-server.js +92 -7
  55. package/dist/production/index-react-server.mjs +92 -7
  56. package/dist/production/index.d.mts +9 -9
  57. package/dist/production/index.d.ts +9 -9
  58. package/dist/production/index.js +205 -101
  59. package/dist/production/index.mjs +7 -3
  60. package/dist/{development/instrumentation-BlrVzjbg.d.ts → production/instrumentation-DvHY1sgY.d.ts} +45 -1
  61. package/dist/production/lib/types/internal.d.mts +2 -2
  62. package/dist/production/lib/types/internal.d.ts +2 -2
  63. package/dist/production/lib/types/internal.js +1 -1
  64. package/dist/production/lib/types/internal.mjs +1 -1
  65. package/dist/production/{register-BGQUMCK4.d.ts → register-Bm80E9qL.d.ts} +1 -1
  66. package/dist/production/{register-DTJJbt1o.d.mts → register-CS_tiXsm.d.mts} +1 -1
  67. package/dist/production/{router-5fbeEIMQ.d.mts → router-5iOvts3c.d.mts} +45 -1
  68. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,6 +1,51 @@
1
1
  # `react-router`
2
2
 
3
- ## 7.11.0-pre.0
3
+ ## 7.12.0-pre.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add additional layer of CSRF protection by rejecting submissions to UI routes from external origins. If you need to permit access to specific external origins, you can specify them in the `react-router.config.ts` config `allowedActionOrigins` field. ([#14708](https://github.com/remix-run/react-router/pull/14708))
8
+
9
+ ### Patch Changes
10
+
11
+ - Fix `generatePath` when used with suffixed params (i.e., "/books/:id.json") ([#14269](https://github.com/remix-run/react-router/pull/14269))
12
+ - Export `UNSAFE_createMemoryHistory` and `UNSAFE_createHashHistory` alongside `UNSAFE_createBrowserHistory` for consistency. These are not intended to be used for new apps but intended to help apps usiong `unstable_HistoryRouter` migrate from v6->v7 so they can adopt the newer APIs. ([#14663](https://github.com/remix-run/react-router/pull/14663))
13
+ - Escape HTML in scroll restoration keys ([#14705](https://github.com/remix-run/react-router/pull/14705))
14
+ - Validate redirect locations ([#14706](https://github.com/remix-run/react-router/pull/14706))
15
+ - [UNSTABLE] Pass `<Scripts nonce>` value through to the underlying `importmap` `script` tag when using `future.unstable_subResourceIntegrity` ([#14675](https://github.com/remix-run/react-router/pull/14675))
16
+ - [UNSTABLE] Add a new `future.unstable_trailingSlashAwareDataRequests` flag to provide consistent behavior of `request.pathname` inside `middleware`, `loader`, and `action` functions on document and data requests when a trailing slash is present in the browser URL. ([#14644](https://github.com/remix-run/react-router/pull/14644))
17
+
18
+ Currently, your HTTP and `request` pathnames would be as follows for `/a/b/c` and `/a/b/c/`
19
+
20
+ | URL `/a/b/c` | **HTTP pathname** | **`request` pathname`** |
21
+ | ------------ | ----------------- | ----------------------- |
22
+ | **Document** | `/a/b/c` | `/a/b/c` ✅ |
23
+ | **Data** | `/a/b/c.data` | `/a/b/c` ✅ |
24
+
25
+ | URL `/a/b/c/` | **HTTP pathname** | **`request` pathname`** |
26
+ | ------------- | ----------------- | ----------------------- |
27
+ | **Document** | `/a/b/c/` | `/a/b/c/` ✅ |
28
+ | **Data** | `/a/b/c.data` | `/a/b/c` ⚠️ |
29
+
30
+ With this flag enabled, these pathnames will be made consistent though a new `_.data` format for client-side `.data` requests:
31
+
32
+ | URL `/a/b/c` | **HTTP pathname** | **`request` pathname`** |
33
+ | ------------ | ----------------- | ----------------------- |
34
+ | **Document** | `/a/b/c` | `/a/b/c` ✅ |
35
+ | **Data** | `/a/b/c.data` | `/a/b/c` ✅ |
36
+
37
+ | URL `/a/b/c/` | **HTTP pathname** | **`request` pathname`** |
38
+ | ------------- | ------------------ | ----------------------- |
39
+ | **Document** | `/a/b/c/` | `/a/b/c/` ✅ |
40
+ | **Data** | `/a/b/c/_.data` ⬅️ | `/a/b/c/` ✅ |
41
+
42
+ This a bug fix but we are putting it behind an opt-in flag because it has the potential to be a "breaking bug fix" if you are relying on the URL format for any other application or caching logic.
43
+
44
+ Enabling this flag also changes the format of client side `.data` requests from `/_root.data` to `/_.data` when navigating to `/` to align with the new format. This does not impact the `request` pathname which is still `/` in all cases.
45
+
46
+ - Preserve `clientLoader.hydrate=true` when using `<HydratedRouter unstable_instrumentations>` ([#14674](https://github.com/remix-run/react-router/pull/14674))
47
+
48
+ ## 7.11.0
4
49
 
5
50
  ### Minor Changes
6
51
 
@@ -9,10 +54,14 @@
9
54
  ### Patch Changes
10
55
 
11
56
  - add support for throwing redirect Response's at RSC render time ([#14596](https://github.com/remix-run/react-router/pull/14596))
57
+
12
58
  - Support for throwing `data()` and Response from server component render phase. Response body is not serialized as async work is not allowed as error encoding phase. If you wish to transmit data to the boundary, throw `data()` instead. ([#14632](https://github.com/remix-run/react-router/pull/14632))
59
+
13
60
  - Fix `unstable_useTransitions` prop on `<Router>` component to permit omission for backewards compatibility ([#14646](https://github.com/remix-run/react-router/pull/14646))
61
+
14
62
  - `routeRSCServerRequest` replace `fetchServer` with `serverResponse` ([#14597](https://github.com/remix-run/react-router/pull/14597))
15
- - [UNSTABLE] Add a new `unstable_defaultShouldRevalidate` flag to various APIs to allow opt-ing out of standard revalidation behaviors. ([#14542](https://github.com/remix-run/react-router/pull/14542))
63
+
64
+ - \[UNSTABLE] Add a new `unstable_defaultShouldRevalidate` flag to various APIs to allow opt-ing out of standard revalidation behaviors. ([#14542](https://github.com/remix-run/react-router/pull/14542))
16
65
 
17
66
  If active routes include a `shouldRevalidate` function, then your value will be passed as `defaultShouldRevalidate` in those function so that the route always has the final revalidation determination.
18
67
  - `<Form method="post" unstable_defaultShouldRevalidate={false}>`
@@ -26,6 +75,7 @@
26
75
  - `setSearchParams(params, { unstable_defaultShouldRevalidate: false })`
27
76
 
28
77
  - Allow redirects to be returned from client side middleware ([#14598](https://github.com/remix-run/react-router/pull/14598))
78
+
29
79
  - Handle `dataStrategy` implementations that return insufficient result sets by adding errors for routes without any available result ([#14627](https://github.com/remix-run/react-router/pull/14627))
30
80
 
31
81
  ## 7.10.1
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { L as Location, C as ClientActionFunction, a as ClientLoaderFunction, b as LinksFunction, M as MetaFunction, S as ShouldRevalidateFunction, P as Params, c as RouterContextProvider, A as ActionFunction, H as HeadersFunction, d as LoaderFunction, e as RouterInit } from './router-5fbeEIMQ.mjs';
2
+ import { L as Location, C as ClientActionFunction, a as ClientLoaderFunction, b as LinksFunction, M as MetaFunction, S as ShouldRevalidateFunction, P as Params, c as RouterContextProvider, A as ActionFunction, H as HeadersFunction, d as LoaderFunction, e as RouterInit } from './router-5iOvts3c.mjs';
3
3
 
4
4
  type RSCRouteConfigEntryBase = {
5
5
  action?: ActionFunction;
@@ -138,6 +138,7 @@ type LoadServerActionFunction = (id: string) => Promise<Function>;
138
138
  * @category RSC
139
139
  * @mode data
140
140
  * @param opts Options
141
+ * @param opts.allowedActionOrigins Origin patterns that are allowed to execute actions.
141
142
  * @param opts.basename The basename to use when matching the request.
142
143
  * @param opts.createTemporaryReferenceSet A function that returns a temporary
143
144
  * reference set for the request, used to track temporary references in the [RSC](https://react.dev/reference/rsc/server-components)
@@ -167,7 +168,8 @@ type LoadServerActionFunction = (id: string) => Promise<Function>;
167
168
  * that contains the [RSC](https://react.dev/reference/rsc/server-components)
168
169
  * data for hydration.
169
170
  */
170
- declare function matchRSCServerRequest({ createTemporaryReferenceSet, basename, decodeReply, requestContext, loadServerAction, decodeAction, decodeFormState, onError, request, routes, generateResponse, }: {
171
+ declare function matchRSCServerRequest({ allowedActionOrigins, createTemporaryReferenceSet, basename, decodeReply, requestContext, loadServerAction, decodeAction, decodeFormState, onError, request, routes, generateResponse, }: {
172
+ allowedActionOrigins?: string[];
171
173
  createTemporaryReferenceSet: () => unknown;
172
174
  basename?: string;
173
175
  decodeReply?: DecodeReplyFunction;
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { L as Location, C as ClientActionFunction, a as ClientLoaderFunction, b as LinksFunction, M as MetaFunction, S as ShouldRevalidateFunction, P as Params, c as RouterContextProvider, A as ActionFunction, H as HeadersFunction, d as LoaderFunction, e as RouterInit } from './router-5fbeEIMQ.mjs';
2
+ import { L as Location, C as ClientActionFunction, a as ClientLoaderFunction, b as LinksFunction, M as MetaFunction, S as ShouldRevalidateFunction, P as Params, c as RouterContextProvider, A as ActionFunction, H as HeadersFunction, d as LoaderFunction, e as RouterInit } from './instrumentation-DvHY1sgY.js';
3
3
 
4
4
  type RSCRouteConfigEntryBase = {
5
5
  action?: ActionFunction;
@@ -138,6 +138,7 @@ type LoadServerActionFunction = (id: string) => Promise<Function>;
138
138
  * @category RSC
139
139
  * @mode data
140
140
  * @param opts Options
141
+ * @param opts.allowedActionOrigins Origin patterns that are allowed to execute actions.
141
142
  * @param opts.basename The basename to use when matching the request.
142
143
  * @param opts.createTemporaryReferenceSet A function that returns a temporary
143
144
  * reference set for the request, used to track temporary references in the [RSC](https://react.dev/reference/rsc/server-components)
@@ -167,7 +168,8 @@ type LoadServerActionFunction = (id: string) => Promise<Function>;
167
168
  * that contains the [RSC](https://react.dev/reference/rsc/server-components)
168
169
  * data for hydration.
169
170
  */
170
- declare function matchRSCServerRequest({ createTemporaryReferenceSet, basename, decodeReply, requestContext, loadServerAction, decodeAction, decodeFormState, onError, request, routes, generateResponse, }: {
171
+ declare function matchRSCServerRequest({ allowedActionOrigins, createTemporaryReferenceSet, basename, decodeReply, requestContext, loadServerAction, decodeAction, decodeFormState, onError, request, routes, generateResponse, }: {
172
+ allowedActionOrigins?: string[];
171
173
  createTemporaryReferenceSet: () => unknown;
172
174
  basename?: string;
173
175
  decodeReply?: DecodeReplyFunction;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v7.11.0-pre.0
2
+ * react-router v7.12.0-pre.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -51,7 +51,7 @@ import {
51
51
  withComponentProps,
52
52
  withErrorBoundaryProps,
53
53
  withHydrateFallbackProps
54
- } from "./chunk-KRMLYMWA.mjs";
54
+ } from "./chunk-KYKH2NCS.mjs";
55
55
 
56
56
  // lib/dom/ssr/server.tsx
57
57
  import * as React from "react";
@@ -138,7 +138,8 @@ function createRoutesStub(routes, _context) {
138
138
  frameworkContextRef.current = {
139
139
  future: {
140
140
  unstable_subResourceIntegrity: future?.unstable_subResourceIntegrity === true,
141
- v8_middleware: future?.v8_middleware === true
141
+ v8_middleware: future?.v8_middleware === true,
142
+ unstable_trailingSlashAwareDataRequests: future?.unstable_trailingSlashAwareDataRequests === true
142
143
  },
143
144
  manifest: {
144
145
  routes: {},
@@ -754,6 +755,85 @@ function prependCookies(parentHeaders, childHeaders) {
754
755
  }
755
756
  }
756
757
 
758
+ // lib/actions.ts
759
+ function throwIfPotentialCSRFAttack(headers, allowedActionOrigins) {
760
+ let originHeader = headers.get("origin");
761
+ let originDomain = typeof originHeader === "string" && originHeader !== "null" ? new URL(originHeader).host : originHeader;
762
+ let host = parseHostHeader(headers);
763
+ if (originDomain && (!host || originDomain !== host.value)) {
764
+ if (!isAllowedOrigin(originDomain, allowedActionOrigins)) {
765
+ if (host) {
766
+ throw new Error(
767
+ `${host.type} header does not match \`origin\` header from a forwarded action request. Aborting the action.`
768
+ );
769
+ } else {
770
+ throw new Error(
771
+ "`x-forwarded-host` or `host` headers are not provided. One of these is needed to compare the `origin` header from a forwarded action request. Aborting the action."
772
+ );
773
+ }
774
+ }
775
+ }
776
+ }
777
+ function matchWildcardDomain(domain, pattern) {
778
+ const domainParts = domain.split(".");
779
+ const patternParts = pattern.split(".");
780
+ if (patternParts.length < 1) {
781
+ return false;
782
+ }
783
+ if (domainParts.length < patternParts.length) {
784
+ return false;
785
+ }
786
+ if (patternParts.length === 1 && (patternParts[0] === "*" || patternParts[0] === "**")) {
787
+ return false;
788
+ }
789
+ while (patternParts.length) {
790
+ const patternPart = patternParts.pop();
791
+ const domainPart = domainParts.pop();
792
+ switch (patternPart) {
793
+ case "": {
794
+ return false;
795
+ }
796
+ case "*": {
797
+ if (domainPart) {
798
+ continue;
799
+ } else {
800
+ return false;
801
+ }
802
+ }
803
+ case "**": {
804
+ if (patternParts.length > 0) {
805
+ return false;
806
+ }
807
+ return domainPart !== void 0;
808
+ }
809
+ case void 0:
810
+ default: {
811
+ if (domainPart !== patternPart) {
812
+ return false;
813
+ }
814
+ }
815
+ }
816
+ }
817
+ return domainParts.length === 0;
818
+ }
819
+ function isAllowedOrigin(originDomain, allowedActionOrigins = []) {
820
+ return allowedActionOrigins.some(
821
+ (allowedOrigin) => allowedOrigin && (allowedOrigin === originDomain || matchWildcardDomain(originDomain, allowedOrigin))
822
+ );
823
+ }
824
+ function parseHostHeader(headers) {
825
+ let forwardedHostHeader = headers.get("x-forwarded-host");
826
+ let forwardedHostValue = forwardedHostHeader?.split(",")[0]?.trim();
827
+ let hostHeader = headers.get("host");
828
+ return forwardedHostValue ? {
829
+ type: "x-forwarded-host",
830
+ value: forwardedHostValue
831
+ } : hostHeader ? {
832
+ type: "host",
833
+ value: hostHeader
834
+ } : void 0;
835
+ }
836
+
757
837
  // lib/server-runtime/single-fetch.ts
758
838
  var SERVER_NO_BODY_STATUS_CODES = /* @__PURE__ */ new Set([
759
839
  ...NO_BODY_STATUS_CODES,
@@ -761,6 +841,10 @@ var SERVER_NO_BODY_STATUS_CODES = /* @__PURE__ */ new Set([
761
841
  ]);
762
842
  async function singleFetchAction(build, serverMode, staticHandler, request, handlerUrl, loadContext, handleError) {
763
843
  try {
844
+ throwIfPotentialCSRFAttack(
845
+ request.headers,
846
+ Array.isArray(build.allowedActionOrigins) ? build.allowedActionOrigins : []
847
+ );
764
848
  let handlerRequest = new Request(handlerUrl, {
765
849
  method: request.method,
766
850
  body: request.body,
@@ -1041,13 +1125,21 @@ function derive(build, mode) {
1041
1125
  let url = new URL(request.url);
1042
1126
  let normalizedBasename = build.basename || "/";
1043
1127
  let normalizedPath = url.pathname;
1044
- if (stripBasename(normalizedPath, normalizedBasename) === "/_root.data") {
1045
- normalizedPath = normalizedBasename;
1046
- } else if (normalizedPath.endsWith(".data")) {
1047
- normalizedPath = normalizedPath.replace(/\.data$/, "");
1048
- }
1049
- if (stripBasename(normalizedPath, normalizedBasename) !== "/" && normalizedPath.endsWith("/")) {
1050
- normalizedPath = normalizedPath.slice(0, -1);
1128
+ if (build.future.unstable_trailingSlashAwareDataRequests) {
1129
+ if (normalizedPath.endsWith("/_.data")) {
1130
+ normalizedPath = normalizedPath.replace(/_.data$/, "");
1131
+ } else {
1132
+ normalizedPath = normalizedPath.replace(/\.data$/, "");
1133
+ }
1134
+ } else {
1135
+ if (stripBasename(normalizedPath, normalizedBasename) === "/_root.data") {
1136
+ normalizedPath = normalizedBasename;
1137
+ } else if (normalizedPath.endsWith(".data")) {
1138
+ normalizedPath = normalizedPath.replace(/\.data$/, "");
1139
+ }
1140
+ if (stripBasename(normalizedPath, normalizedBasename) !== "/" && normalizedPath.endsWith("/")) {
1141
+ normalizedPath = normalizedPath.slice(0, -1);
1142
+ }
1051
1143
  }
1052
1144
  let isSpaMode = getBuildTimeHeader(request, "X-React-Router-SPA-Mode") === "yes";
1053
1145
  if (!build.ssr) {
@@ -1303,6 +1395,12 @@ async function handleSingleFetchRequest(serverMode, build, staticHandler, reques
1303
1395
  }
1304
1396
  async function handleDocumentRequest(serverMode, build, staticHandler, request, loadContext, handleError, isSpaMode, criticalCss) {
1305
1397
  try {
1398
+ if (request.method === "POST") {
1399
+ throwIfPotentialCSRFAttack(
1400
+ request.headers,
1401
+ Array.isArray(build.allowedActionOrigins) ? build.allowedActionOrigins : []
1402
+ );
1403
+ }
1306
1404
  let result = await staticHandler.query(request, {
1307
1405
  requestContext: loadContext,
1308
1406
  generateMiddlewareResponse: build.future.v8_middleware ? async (query) => {
@@ -2274,7 +2372,9 @@ function RSCStaticRouter({ getPayload }) {
2274
2372
  // These flags have no runtime impact so can always be false. If we add
2275
2373
  // flags that drive runtime behavior they'll need to be proxied through.
2276
2374
  v8_middleware: false,
2277
- unstable_subResourceIntegrity: false
2375
+ unstable_subResourceIntegrity: false,
2376
+ unstable_trailingSlashAwareDataRequests: true
2377
+ // always on for RSC
2278
2378
  },
2279
2379
  isSpaMode: false,
2280
2380
  ssr: true,