react-router 8.2.0 → 8.3.1

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 (175) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/dist/development/dom-export.js +1 -1
  3. package/dist/development/index-react-server-client.js +1 -1
  4. package/dist/development/index-react-server.d.ts +114 -17
  5. package/dist/development/index-react-server.js +189 -54
  6. package/dist/development/index.js +1 -1
  7. package/dist/development/lib/actions.js +10 -5
  8. package/dist/development/lib/components.js +6 -3
  9. package/dist/development/lib/context.d.ts +1 -0
  10. package/dist/development/lib/context.js +1 -1
  11. package/dist/development/lib/dom/dom.d.ts +24 -24
  12. package/dist/development/lib/dom/dom.js +26 -26
  13. package/dist/development/lib/dom/lib.d.ts +2 -1
  14. package/dist/development/lib/dom/lib.js +23 -7
  15. package/dist/development/lib/dom/server.js +1 -1
  16. package/dist/development/lib/dom/ssr/components.js +2 -2
  17. package/dist/development/lib/dom/ssr/data.js +1 -1
  18. package/dist/development/lib/dom/ssr/errorBoundaries.js +1 -1
  19. package/dist/development/lib/dom/ssr/fallback.js +1 -1
  20. package/dist/development/lib/dom/ssr/fog-of-war.js +27 -21
  21. package/dist/development/lib/dom/ssr/hydration.js +1 -1
  22. package/dist/development/lib/dom/ssr/invariant.js +1 -1
  23. package/dist/development/lib/dom/ssr/links.js +1 -1
  24. package/dist/development/lib/dom/ssr/markup.js +1 -1
  25. package/dist/development/lib/dom/ssr/routeModules.js +1 -1
  26. package/dist/development/lib/dom/ssr/routes-test-stub.d.ts +13 -0
  27. package/dist/development/lib/dom/ssr/routes-test-stub.js +14 -1
  28. package/dist/development/lib/dom/ssr/routes.js +1 -1
  29. package/dist/development/lib/dom/ssr/server.js +1 -1
  30. package/dist/development/lib/dom/ssr/single-fetch.js +5 -5
  31. package/dist/development/lib/dom-export/dom-router-provider.js +1 -1
  32. package/dist/development/lib/dom-export/hydrated-router.js +1 -1
  33. package/dist/development/lib/errors.js +1 -1
  34. package/dist/development/lib/hooks.js +6 -3
  35. package/dist/development/lib/href.d.ts +24 -8
  36. package/dist/development/lib/href.js +28 -11
  37. package/dist/development/lib/router/history.d.ts +6 -0
  38. package/dist/development/lib/router/history.js +8 -2
  39. package/dist/development/lib/router/instrumentation.js +1 -1
  40. package/dist/development/lib/router/navigation.js +45 -0
  41. package/dist/development/lib/router/router.d.ts +8 -0
  42. package/dist/development/lib/router/router.js +31 -17
  43. package/dist/development/lib/router/url.js +1 -1
  44. package/dist/development/lib/router/utils.d.ts +12 -0
  45. package/dist/development/lib/router/utils.js +66 -8
  46. package/dist/development/lib/rsc/browser.js +37 -24
  47. package/dist/development/lib/rsc/errorBoundaries.js +1 -1
  48. package/dist/development/lib/rsc/html-stream/browser.js +1 -1
  49. package/dist/development/lib/rsc/html-stream/server.js +30 -11
  50. package/dist/development/lib/rsc/route-modules.js +1 -1
  51. package/dist/development/lib/rsc/server.rsc.d.ts +5 -1
  52. package/dist/development/lib/rsc/server.ssr.d.ts +29 -6
  53. package/dist/development/lib/rsc/server.ssr.js +28 -13
  54. package/dist/development/lib/server-runtime/cookies.d.ts +28 -2
  55. package/dist/development/lib/server-runtime/cookies.js +18 -4
  56. package/dist/development/lib/server-runtime/crypto.js +2 -2
  57. package/dist/development/lib/server-runtime/data.js +1 -1
  58. package/dist/development/lib/server-runtime/dev.js +2 -2
  59. package/dist/development/lib/server-runtime/entry.js +1 -1
  60. package/dist/development/lib/server-runtime/errors.js +1 -1
  61. package/dist/development/lib/server-runtime/headers.js +1 -1
  62. package/dist/development/lib/server-runtime/invariant.js +1 -1
  63. package/dist/development/lib/server-runtime/mode.js +1 -1
  64. package/dist/development/lib/server-runtime/routeMatching.js +1 -1
  65. package/dist/development/lib/server-runtime/routes.js +1 -1
  66. package/dist/development/lib/server-runtime/server.d.ts +12 -0
  67. package/dist/development/lib/server-runtime/server.js +14 -2
  68. package/dist/development/lib/server-runtime/serverHandoff.js +1 -1
  69. package/dist/development/lib/server-runtime/sessions/cookieStorage.d.ts +8 -0
  70. package/dist/development/lib/server-runtime/sessions/cookieStorage.js +9 -1
  71. package/dist/development/lib/server-runtime/sessions/memoryStorage.d.ts +10 -4
  72. package/dist/development/lib/server-runtime/sessions/memoryStorage.js +12 -6
  73. package/dist/development/lib/server-runtime/sessions.d.ts +31 -2
  74. package/dist/development/lib/server-runtime/sessions.js +20 -3
  75. package/dist/development/lib/server-runtime/single-fetch.js +2 -2
  76. package/dist/development/lib/server-runtime/urls.js +1 -1
  77. package/dist/development/lib/server-runtime/warnings.js +1 -1
  78. package/dist/development/lib/types/internal.js +1 -1
  79. package/dist/development/vendor/turbo-stream-v2/flatten.js +1 -1
  80. package/dist/development/vendor/turbo-stream-v2/turbo-stream.js +4 -4
  81. package/dist/development/vendor/turbo-stream-v2/unflatten.js +1 -1
  82. package/dist/development/vendor/turbo-stream-v2/utils.js +1 -1
  83. package/dist/production/dom-export.js +1 -1
  84. package/dist/production/index-react-server-client.js +1 -1
  85. package/dist/production/index-react-server.d.ts +114 -17
  86. package/dist/production/index-react-server.js +189 -54
  87. package/dist/production/index.js +1 -1
  88. package/dist/production/lib/actions.js +10 -5
  89. package/dist/production/lib/components.js +6 -3
  90. package/dist/production/lib/context.d.ts +1 -0
  91. package/dist/production/lib/context.js +1 -1
  92. package/dist/production/lib/dom/dom.d.ts +24 -24
  93. package/dist/production/lib/dom/dom.js +26 -26
  94. package/dist/production/lib/dom/lib.d.ts +2 -1
  95. package/dist/production/lib/dom/lib.js +23 -7
  96. package/dist/production/lib/dom/server.js +1 -1
  97. package/dist/production/lib/dom/ssr/components.js +2 -2
  98. package/dist/production/lib/dom/ssr/data.js +1 -1
  99. package/dist/production/lib/dom/ssr/errorBoundaries.js +1 -1
  100. package/dist/production/lib/dom/ssr/fallback.js +1 -1
  101. package/dist/production/lib/dom/ssr/fog-of-war.js +27 -21
  102. package/dist/production/lib/dom/ssr/hydration.js +1 -1
  103. package/dist/production/lib/dom/ssr/invariant.js +1 -1
  104. package/dist/production/lib/dom/ssr/links.js +1 -1
  105. package/dist/production/lib/dom/ssr/markup.js +1 -1
  106. package/dist/production/lib/dom/ssr/routeModules.js +1 -1
  107. package/dist/production/lib/dom/ssr/routes-test-stub.d.ts +13 -0
  108. package/dist/production/lib/dom/ssr/routes-test-stub.js +14 -1
  109. package/dist/production/lib/dom/ssr/routes.js +1 -1
  110. package/dist/production/lib/dom/ssr/server.js +1 -1
  111. package/dist/production/lib/dom/ssr/single-fetch.js +5 -5
  112. package/dist/production/lib/dom-export/dom-router-provider.js +1 -1
  113. package/dist/production/lib/dom-export/hydrated-router.js +1 -1
  114. package/dist/production/lib/errors.js +1 -1
  115. package/dist/production/lib/hooks.js +6 -3
  116. package/dist/production/lib/href.d.ts +24 -8
  117. package/dist/production/lib/href.js +28 -11
  118. package/dist/production/lib/router/history.d.ts +6 -0
  119. package/dist/production/lib/router/history.js +8 -2
  120. package/dist/production/lib/router/instrumentation.js +1 -1
  121. package/dist/production/lib/router/navigation.js +45 -0
  122. package/dist/production/lib/router/router.d.ts +8 -0
  123. package/dist/production/lib/router/router.js +31 -17
  124. package/dist/production/lib/router/url.js +1 -1
  125. package/dist/production/lib/router/utils.d.ts +12 -0
  126. package/dist/production/lib/router/utils.js +66 -8
  127. package/dist/production/lib/rsc/browser.js +37 -24
  128. package/dist/production/lib/rsc/errorBoundaries.js +1 -1
  129. package/dist/production/lib/rsc/html-stream/browser.js +1 -1
  130. package/dist/production/lib/rsc/html-stream/server.js +30 -11
  131. package/dist/production/lib/rsc/route-modules.js +1 -1
  132. package/dist/production/lib/rsc/server.rsc.d.ts +5 -1
  133. package/dist/production/lib/rsc/server.ssr.d.ts +29 -6
  134. package/dist/production/lib/rsc/server.ssr.js +28 -13
  135. package/dist/production/lib/server-runtime/cookies.d.ts +28 -2
  136. package/dist/production/lib/server-runtime/cookies.js +18 -4
  137. package/dist/production/lib/server-runtime/crypto.js +2 -2
  138. package/dist/production/lib/server-runtime/data.js +1 -1
  139. package/dist/production/lib/server-runtime/dev.js +2 -2
  140. package/dist/production/lib/server-runtime/entry.js +1 -1
  141. package/dist/production/lib/server-runtime/errors.js +1 -1
  142. package/dist/production/lib/server-runtime/headers.js +1 -1
  143. package/dist/production/lib/server-runtime/invariant.js +1 -1
  144. package/dist/production/lib/server-runtime/mode.js +1 -1
  145. package/dist/production/lib/server-runtime/routeMatching.js +1 -1
  146. package/dist/production/lib/server-runtime/routes.js +1 -1
  147. package/dist/production/lib/server-runtime/server.d.ts +12 -0
  148. package/dist/production/lib/server-runtime/server.js +14 -2
  149. package/dist/production/lib/server-runtime/serverHandoff.js +1 -1
  150. package/dist/production/lib/server-runtime/sessions/cookieStorage.d.ts +8 -0
  151. package/dist/production/lib/server-runtime/sessions/cookieStorage.js +9 -1
  152. package/dist/production/lib/server-runtime/sessions/memoryStorage.d.ts +10 -4
  153. package/dist/production/lib/server-runtime/sessions/memoryStorage.js +12 -6
  154. package/dist/production/lib/server-runtime/sessions.d.ts +31 -2
  155. package/dist/production/lib/server-runtime/sessions.js +20 -3
  156. package/dist/production/lib/server-runtime/single-fetch.js +2 -2
  157. package/dist/production/lib/server-runtime/urls.js +1 -1
  158. package/dist/production/lib/server-runtime/warnings.js +1 -1
  159. package/dist/production/lib/types/internal.js +1 -1
  160. package/dist/production/vendor/turbo-stream-v2/flatten.js +1 -1
  161. package/dist/production/vendor/turbo-stream-v2/turbo-stream.js +4 -4
  162. package/dist/production/vendor/turbo-stream-v2/unflatten.js +1 -1
  163. package/dist/production/vendor/turbo-stream-v2/utils.js +1 -1
  164. package/docs/explanation/sessions-and-cookies.md +13 -13
  165. package/docs/explanation/state-management.md +2 -2
  166. package/docs/how-to/presets.md +2 -2
  167. package/docs/how-to/react-server-components.md +53 -1
  168. package/docs/how-to/security.md +8 -1
  169. package/docs/how-to/server-bundles.md +2 -2
  170. package/docs/start/data/route-object.md +2 -2
  171. package/docs/start/data/routing.md +1 -1
  172. package/docs/start/framework/pending-ui.md +1 -1
  173. package/docs/start/framework/route-module.md +8 -8
  174. package/docs/start/framework/routing.md +1 -1
  175. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.2.0
2
+ * react-router v8.3.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -24,7 +24,7 @@ function warning(cond, message) {
24
24
  if (typeof console !== "undefined") console.warn(message);
25
25
  try {
26
26
  throw new Error(message);
27
- } catch (e) {}
27
+ } catch {}
28
28
  }
29
29
  }
30
30
  function createKey$1() {
@@ -47,7 +47,10 @@ function createLocation(current, to, state = null, key, mask) {
47
47
  /**
48
48
  * Creates a string URL path from the given pathname, search, and hash components.
49
49
  *
50
+ * @public
50
51
  * @category Utils
52
+ * @param path The pathname, search, and hash components to combine.
53
+ * @returns The combined URL path.
51
54
  */
52
55
  function createPath({ pathname = "/", search = "", hash = "" }) {
53
56
  if (search && search !== "?") pathname += search.charAt(0) === "?" ? search : "?" + search;
@@ -57,7 +60,10 @@ function createPath({ pathname = "/", search = "", hash = "" }) {
57
60
  /**
58
61
  * Parses a string URL path into its separate pathname, search, and hash components.
59
62
  *
63
+ * @public
60
64
  * @category Utils
65
+ * @param path The URL path to parse.
66
+ * @returns The parsed pathname, search, and hash components.
61
67
  */
62
68
  function parsePath(path) {
63
69
  let parsedPath = {};
@@ -441,6 +447,48 @@ function matchRouteBranch(branch, pathname, allowPartial = false) {
441
447
  return matches;
442
448
  }
443
449
  /**
450
+ * Characters that `encodeURIComponent` escapes but that are valid literally in
451
+ * a URL path segment. Per RFC 3986 §3.3, a path segment is made of `pchar`:
452
+ *
453
+ * ```
454
+ * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
455
+ * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
456
+ * ```
457
+ *
458
+ * `encodeURIComponent` targets query-string values, where `$ & + , ; = : @`
459
+ * are delimiters and must be escaped — but in a path segment they carry no
460
+ * special meaning, and browsers keep them literal in `location.pathname`.
461
+ * (`! ' ( ) *` and the unreserved set are already left alone by
462
+ * `encodeURIComponent`, so they need no restoring.)
463
+ */
464
+ const PATH_PARAM_OVERESCAPED = {
465
+ "%24": "$",
466
+ "%26": "&",
467
+ "%2B": "+",
468
+ "%2C": ",",
469
+ "%3A": ":",
470
+ "%3B": ";",
471
+ "%3D": "=",
472
+ "%40": "@"
473
+ };
474
+ /**
475
+ * Encodes a param value for interpolation into a single URL path segment.
476
+ *
477
+ * Escapes characters that would break the path (`/ ? # %`, whitespace,
478
+ * non-ASCII, …) while leaving characters that RFC 3986 permits literally in a
479
+ * path segment untouched. Escaping those would needlessly rewrite URLs — e.g.
480
+ * a semver build param `1.0.0+1` would become `1.0.0%2B1` even though browsers
481
+ * display and match the `+` literally in `location.pathname`.
482
+ *
483
+ * See [RFC 3986 §3.3](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3))
484
+ *
485
+ * @param value The param value to encode.
486
+ * @returns The encoded value, safe for use as a single path segment.
487
+ */
488
+ function encodePathParam(value) {
489
+ return encodeURIComponent(value).replace(/%(?:24|26|2B|2C|3A|3B|3D|40)/g, (match) => PATH_PARAM_OVERESCAPED[match]);
490
+ }
491
+ /**
444
492
  * Performs pattern matching on a URL pathname and returns information about
445
493
  * the match.
446
494
  *
@@ -467,13 +515,13 @@ function matchPathImpl(pattern, pathname, matcher, compiledParams) {
467
515
  let match = pathname.match(matcher);
468
516
  if (!match) return null;
469
517
  let matchedPathname = match[0];
470
- let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
518
+ let pathnameBase = removeTrailingSlash(matchedPathname, 1);
471
519
  let captureGroups = match.slice(1);
472
520
  return {
473
521
  params: compiledParams.reduce((memo, { paramName, isOptional }, index) => {
474
522
  if (paramName === "*") {
475
523
  let splatValue = captureGroups[index] || "";
476
- pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
524
+ pathnameBase = removeTrailingSlash(matchedPathname.slice(0, matchedPathname.length - splatValue.length), 1);
477
525
  }
478
526
  const value = captureGroups[index];
479
527
  if (isOptional && !value) memo[paramName] = void 0;
@@ -542,7 +590,7 @@ function resolvePath(to, fromPathname = "/") {
542
590
  let pathname;
543
591
  if (toPathname) {
544
592
  toPathname = removeDoubleSlashes(toPathname);
545
- if (toPathname.startsWith("/")) pathname = resolvePathname(toPathname.substring(1), "/");
593
+ if (toPathname.startsWith("/") || toPathname.startsWith("\\")) pathname = resolvePathname(toPathname.substring(1), "/");
546
594
  else pathname = resolvePathname(toPathname, fromPathname);
547
595
  } else pathname = fromPathname;
548
596
  return {
@@ -603,7 +651,11 @@ function resolveTo(toArg, routePathnames, locationPathname, isPathRelative = fal
603
651
  }
604
652
  const removeDoubleSlashes = (path) => path.replace(/[\\/]{2,}/g, "/");
605
653
  const joinPaths = (paths) => removeDoubleSlashes(paths.join("/"));
606
- const removeTrailingSlash = (path) => path.replace(/\/+$/, "");
654
+ function removeTrailingSlash(path, minLength = 0) {
655
+ let end = path.length;
656
+ while (end > minLength && path.charCodeAt(end - 1) === 47) end--;
657
+ return end === path.length ? path : path.slice(0, end);
658
+ }
607
659
  const normalizePathname = (pathname) => removeTrailingSlash(pathname).replace(/^\/*/, "/");
608
660
  const normalizeSearch = (search) => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search;
609
661
  const normalizeHash = (hash) => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash;
@@ -1768,7 +1820,7 @@ async function callDataStrategyImpl(dataStrategyImpl, request, path, matches, fe
1768
1820
  });
1769
1821
  try {
1770
1822
  await Promise.all(matches.flatMap((m) => [m._lazyPromises?.handler, m._lazyPromises?.route]));
1771
- } catch (e) {}
1823
+ } catch {}
1772
1824
  return results;
1773
1825
  }
1774
1826
  async function callLoaderOrAction({ request, path, pattern, match, lazyHandlerPromise, lazyRoutePromise, handlerOverride, scopedContext }) {
@@ -2169,14 +2221,19 @@ function getPathsWithAncestors(paths) {
2169
2221
  function throwIfPotentialCSRFAttack(request, allowedActionOrigins) {
2170
2222
  let originHeader = request.headers.get("origin");
2171
2223
  let originDomain = null;
2224
+ let originUrl = null;
2172
2225
  try {
2173
- originDomain = typeof originHeader === "string" && originHeader !== "null" ? new URL(originHeader).host : originHeader;
2226
+ if (typeof originHeader === "string" && originHeader !== "null") {
2227
+ originUrl = new URL(originHeader);
2228
+ originDomain = originUrl.host;
2229
+ } else originDomain = originHeader;
2174
2230
  } catch {
2175
2231
  throw new Error(`\`origin\` header is not a valid URL. Aborting the action.`);
2176
2232
  }
2177
- let host = new URL(request.url).host;
2178
- if (originDomain && originDomain !== host) {
2179
- if (!isAllowedOrigin(originDomain, allowedActionOrigins)) throw new Error("The `request.url` host does not match `origin` header from a forwarded action request. Aborting the action.");
2233
+ let requestUrl = new URL(request.url);
2234
+ let originMatchesRequest = originUrl ? originUrl.origin === requestUrl.origin : originDomain === requestUrl.host;
2235
+ if (originDomain && !originMatchesRequest) {
2236
+ if (!isAllowedOrigin(originDomain, allowedActionOrigins)) throw new Error("The `request.url` origin does not match `origin` header from a forwarded action request. Aborting the action.");
2180
2237
  }
2181
2238
  }
2182
2239
  function matchWildcardDomain(domain, pattern) {
@@ -2332,6 +2389,8 @@ const Await = (async ({ children, resolve, errorElement }) => {
2332
2389
  * encoding the {@link unstable_RSCPayload}.
2333
2390
  * @param opts.loadServerAction Your `react-server-dom-xyz/server`'s
2334
2391
  * `loadServerAction` function, used to load a server action by ID.
2392
+ * @param opts.clientVersion A version derived from the client build output used
2393
+ * to detect stale clients during lazy route discovery.
2335
2394
  * @param opts.onError An optional error handler that will be called with any
2336
2395
  * errors that occur during the request processing.
2337
2396
  * @param opts.request The [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
@@ -2345,7 +2404,7 @@ const Await = (async ({ children, resolve, errorElement }) => {
2345
2404
  * that contains the [RSC](https://react.dev/reference/rsc/server-components)
2346
2405
  * data for hydration.
2347
2406
  */
2348
- async function matchRSCServerRequest({ allowedActionOrigins, createTemporaryReferenceSet, basename, decodeReply, requestContext, routeDiscovery, loadServerAction, decodeAction, decodeFormState, onError, request, routes, generateResponse }) {
2407
+ async function matchRSCServerRequest({ allowedActionOrigins, createTemporaryReferenceSet, basename, decodeReply, requestContext, routeDiscovery, loadServerAction, decodeAction, decodeFormState, clientVersion, onError, request, routes, generateResponse }) {
2349
2408
  let url = new URL(request.url);
2350
2409
  basename = basename || "/";
2351
2410
  let normalizedPath = url.pathname;
@@ -2363,22 +2422,26 @@ async function matchRSCServerRequest({ allowedActionOrigins, createTemporaryRefe
2363
2422
  });
2364
2423
  const temporaryReferences = createTemporaryReferenceSet();
2365
2424
  const requestUrl = new URL(request.url);
2366
- if (isManifestRequest(requestUrl)) return await generateManifestResponse(routes, basename, request, generateResponse, temporaryReferences, routeDiscovery);
2425
+ if (isManifestRequest(requestUrl)) return await generateManifestResponse(routes, basename, request, generateResponse, temporaryReferences, routeDiscovery, clientVersion);
2367
2426
  let isDataRequest = isReactServerRequest(requestUrl);
2368
2427
  let matches = matchRoutes(routes, url.pathname, basename);
2369
2428
  if (matches) await Promise.all(matches.map((m) => explodeLazyRoute(m.route)));
2370
2429
  const leafMatch = matches?.[matches.length - 1];
2371
2430
  if (!isDataRequest && leafMatch && !leafMatch.route.Component && !leafMatch.route.ErrorBoundary) return generateResourceResponse(routerRequest, routes, basename, leafMatch.route.id, requestContext, onError);
2372
- let response = await generateRenderResponse(routerRequest, routes, basename, isDataRequest, decodeReply, requestContext, loadServerAction, decodeAction, decodeFormState, onError, generateResponse, temporaryReferences, allowedActionOrigins, routeDiscovery);
2431
+ let response = await generateRenderResponse(routerRequest, routes, basename, isDataRequest, decodeReply, requestContext, loadServerAction, decodeAction, decodeFormState, onError, generateResponse, temporaryReferences, allowedActionOrigins, routeDiscovery, clientVersion);
2373
2432
  response.headers.set("X-Remix-Response", "yes");
2374
2433
  return response;
2375
2434
  }
2376
- async function generateManifestResponse(routes, basename, request, generateResponse, temporaryReferences, routeDiscovery) {
2435
+ async function generateManifestResponse(routes, basename, request, generateResponse, temporaryReferences, routeDiscovery, clientVersion) {
2377
2436
  let url = new URL(request.url);
2378
2437
  if (url.toString().length > 7680) return new Response(null, {
2379
2438
  statusText: "Bad Request",
2380
2439
  status: 400
2381
2440
  });
2441
+ if (clientVersion !== void 0 && clientVersion !== url.searchParams.get("version")) return new Response(null, {
2442
+ status: 204,
2443
+ headers: { "X-Remix-Reload-Document": "true" }
2444
+ });
2382
2445
  if (routeDiscovery?.mode === "initial") {
2383
2446
  let payload = {
2384
2447
  type: "manifest",
@@ -2527,7 +2590,7 @@ async function generateResourceResponse(request, routes, basename, routeId, requ
2527
2590
  });
2528
2591
  }
2529
2592
  }
2530
- async function generateRenderResponse(request, routes, basename, isDataRequest, decodeReply, requestContext, loadServerAction, decodeAction, decodeFormState, onError, generateResponse, temporaryReferences, allowedActionOrigins, routeDiscovery) {
2593
+ async function generateRenderResponse(request, routes, basename, isDataRequest, decodeReply, requestContext, loadServerAction, decodeAction, decodeFormState, onError, generateResponse, temporaryReferences, allowedActionOrigins, routeDiscovery, clientVersion) {
2531
2594
  let statusCode = 200;
2532
2595
  let url = new URL(request.url);
2533
2596
  let isSubmission = isMutationMethod(request.method);
@@ -2548,29 +2611,39 @@ async function generateRenderResponse(request, routes, basename, isDataRequest,
2548
2611
  let formState;
2549
2612
  let skipRevalidation = false;
2550
2613
  let potentialCSRFAttackError;
2551
- if (isMutationMethod(request.method)) try {
2552
- throwIfPotentialCSRFAttack(request, allowedActionOrigins);
2553
- ctx.runningAction = true;
2554
- let result = await processServerAction(request, basename, decodeReply, loadServerAction, decodeAction, decodeFormState, onError, temporaryReferences).finally(() => {
2555
- ctx.runningAction = false;
2556
- });
2557
- if (isResponse(result)) return generateRedirectResponse(result, actionResult, basename, isDataRequest, generateResponse, temporaryReferences, ctx.redirect?.headers);
2558
- skipRevalidation = result?.skipRevalidation ?? false;
2559
- actionResult = result?.actionResult;
2560
- formState = result?.formState;
2561
- request = result?.revalidationRequest ?? request;
2562
- if (ctx.redirect) return generateRedirectResponse(ctx.redirect, actionResult, basename, isDataRequest, generateResponse, temporaryReferences, void 0);
2563
- } catch (error) {
2564
- potentialCSRFAttackError = error;
2614
+ if (isMutationMethod(request.method)) {
2615
+ try {
2616
+ throwIfPotentialCSRFAttack(request, allowedActionOrigins);
2617
+ } catch (error) {
2618
+ onError?.(error);
2619
+ potentialCSRFAttackError = error;
2620
+ request = new Request(request.url, {
2621
+ method: "GET",
2622
+ headers: request.headers,
2623
+ signal: request.signal
2624
+ });
2625
+ }
2626
+ if (!potentialCSRFAttackError) {
2627
+ ctx.runningAction = true;
2628
+ let result = await processServerAction(request, basename, decodeReply, loadServerAction, decodeAction, decodeFormState, onError, temporaryReferences).finally(() => {
2629
+ ctx.runningAction = false;
2630
+ });
2631
+ if (isResponse(result)) return generateRedirectResponse(result, actionResult, basename, isDataRequest, generateResponse, temporaryReferences, ctx.redirect?.headers);
2632
+ skipRevalidation = result?.skipRevalidation ?? false;
2633
+ actionResult = result?.actionResult;
2634
+ formState = result?.formState;
2635
+ request = result?.revalidationRequest ?? request;
2636
+ if (ctx.redirect) return generateRedirectResponse(ctx.redirect, actionResult, basename, isDataRequest, generateResponse, temporaryReferences, void 0);
2637
+ }
2565
2638
  }
2566
- let staticContext = await query(request, skipRevalidation || !!potentialCSRFAttackError ? { filterMatchesToLoad: () => false } : void 0);
2639
+ let staticContext = await query(request, skipRevalidation ? { filterMatchesToLoad: () => false } : void 0);
2567
2640
  if (isResponse(staticContext)) return generateRedirectResponse(staticContext, actionResult, basename, isDataRequest, generateResponse, temporaryReferences, ctx.redirect?.headers);
2568
2641
  if (potentialCSRFAttackError) {
2569
2642
  staticContext.errors ??= {};
2570
2643
  staticContext.errors[staticContext.matches[0].route.id] = potentialCSRFAttackError;
2571
2644
  staticContext.statusCode = 400;
2572
2645
  }
2573
- return generateStaticContextResponse(routes, basename, generateResponse, statusCode, routeIdsToLoad, isDataRequest, isSubmission, actionResult, formState, staticContext, temporaryReferences, skipRevalidation, ctx.redirect?.headers, routeDiscovery);
2646
+ return generateStaticContextResponse(routes, basename, generateResponse, statusCode, routeIdsToLoad, isDataRequest, isSubmission, actionResult, formState, staticContext, temporaryReferences, skipRevalidation, ctx.redirect?.headers, routeDiscovery, clientVersion);
2574
2647
  }
2575
2648
  }));
2576
2649
  if (isRedirectResponse(result)) return generateRedirectResponse(result, actionResult, basename, isDataRequest, generateResponse, temporaryReferences, ctx.redirect?.headers);
@@ -2604,7 +2677,7 @@ function generateRedirectResponse(response, actionResult, basename, isDataReques
2604
2677
  onError: defaultOnError
2605
2678
  });
2606
2679
  }
2607
- async function generateStaticContextResponse(routes, basename, generateResponse, statusCode, routeIdsToLoad, isDataRequest, isSubmission, actionResult, formState, staticContext, temporaryReferences, skipRevalidation, sideEffectRedirectHeaders, routeDiscovery) {
2680
+ async function generateStaticContextResponse(routes, basename, generateResponse, statusCode, routeIdsToLoad, isDataRequest, isSubmission, actionResult, formState, staticContext, temporaryReferences, skipRevalidation, sideEffectRedirectHeaders, routeDiscovery, clientVersion) {
2608
2681
  statusCode = staticContext.statusCode ?? statusCode;
2609
2682
  if (staticContext.errors) staticContext.errors = Object.fromEntries(Object.entries(staticContext.errors).map(([key, error]) => [key, isRouteErrorResponse(error) ? Object.fromEntries(Object.entries(error)) : error]));
2610
2683
  staticContext.matches.forEach((m) => {
@@ -2617,6 +2690,7 @@ async function generateStaticContextResponse(routes, basename, generateResponse,
2617
2690
  const baseRenderPayload = {
2618
2691
  type: "render",
2619
2692
  basename: staticContext.basename,
2693
+ clientVersion,
2620
2694
  routeDiscovery: routeDiscovery ?? { mode: "lazy" },
2621
2695
  actionData: staticContext.actionData,
2622
2696
  errors: staticContext.errors,
@@ -2812,14 +2886,30 @@ function stringify(p) {
2812
2886
  return p == null ? "" : typeof p === "string" ? p : String(p);
2813
2887
  }
2814
2888
  /**
2815
- Returns a resolved URL path for the specified route.
2816
-
2817
- ```tsx
2818
- const h = href("/:lang?/about", { lang: "en" })
2819
- // -> `/en/about`
2820
-
2821
- <Link to={href("/products/:id", { id: "abc123" })} />
2822
- ```
2889
+ * Returns a resolved URL path for the specified route.
2890
+ *
2891
+ * Param values are percent-encoded for use in a path segment: characters that
2892
+ * would change the URL structure (`/`, `?`, `#`, `%`, whitespace, non-ASCII)
2893
+ * are escaped, while characters that RFC 3986 allows literally in a path
2894
+ * segment (`$ & + , ; = : @`) are kept as-is. Note this differs from query-string
2895
+ * encoding (`encodeURIComponent`/`URLSearchParams`), where those characters are
2896
+ * delimiters and must be escaped. Splat (`*`) values are encoded per segment,
2897
+ * preserving `/` separators.
2898
+ *
2899
+ * See [RFC 3986 §3.3](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3)
2900
+ *
2901
+ * @example
2902
+ * const h = href("/:lang?/about", { lang: "en" })
2903
+ * // -> `/en/about`
2904
+ *
2905
+ * <Link to={href("/products/:id", { id: "abc123" })} />
2906
+ *
2907
+ * @public
2908
+ * @category Utils
2909
+ * @mode framework
2910
+ * @param path The route path to resolve
2911
+ * @param args The route params to use when resolving the path
2912
+ * @returns The resolved URL path
2823
2913
  */
2824
2914
  function href(path, ...args) {
2825
2915
  let params = args[0];
@@ -2827,11 +2917,11 @@ function href(path, ...args) {
2827
2917
  const isRequired = questionMark === void 0;
2828
2918
  const value = params?.[param];
2829
2919
  if (isRequired && value === void 0) throw new Error(`Path '${path}' requires param '${param}' but it was not provided`);
2830
- return value == null ? "" : "/" + encodeURIComponent(stringify(value));
2920
+ return value == null ? "" : "/" + encodePathParam(stringify(value));
2831
2921
  });
2832
2922
  if (path.endsWith("*")) {
2833
2923
  const value = params?.["*"];
2834
- if (value !== void 0) result += "/" + stringify(value).split("/").map(encodeURIComponent).join("/");
2924
+ if (value !== void 0) result += "/" + stringify(value).split("/").map(encodePathParam).join("/");
2835
2925
  }
2836
2926
  return result || "/";
2837
2927
  }
@@ -2867,7 +2957,7 @@ const unsign = async (cookie, secret) => {
2867
2957
  try {
2868
2958
  let signature = byteStringToUint8Array(atob(hash));
2869
2959
  return await crypto.subtle.verify("HMAC", key, signature, data) ? value : false;
2870
- } catch (e) {
2960
+ } catch {
2871
2961
  return false;
2872
2962
  }
2873
2963
  };
@@ -2884,6 +2974,14 @@ function byteStringToUint8Array(byteString) {
2884
2974
  //#region lib/server-runtime/cookies.ts
2885
2975
  /**
2886
2976
  * Creates a logical container for managing a browser cookie from the server.
2977
+ *
2978
+ * @public
2979
+ * @category Utils
2980
+ * @mode framework
2981
+ * @mode data
2982
+ * @param name The name of the cookie.
2983
+ * @param cookieOptions Options for parsing and serializing the cookie.
2984
+ * @returns A {@link Cookie} object for parsing and serializing the cookie.
2887
2985
  */
2888
2986
  const createCookie = (name, cookieOptions = {}) => {
2889
2987
  let { secrets = [], ...options } = {
@@ -2923,9 +3021,15 @@ const createCookie = (name, cookieOptions = {}) => {
2923
3021
  };
2924
3022
  };
2925
3023
  /**
2926
- * Returns true if an object is a Remix cookie container.
3024
+ * Returns `true` if a value is a React Router {@link Cookie} object.
2927
3025
  *
2928
- * @see https://remix.run/utils/cookies#iscookie
3026
+ * @public
3027
+ * @category Utils
3028
+ * @mode framework
3029
+ * @mode data
3030
+ * @param object The value to check.
3031
+ * @returns `true` if the value is a React Router {@link Cookie} object;
3032
+ * otherwise, `false`.
2929
3033
  */
2930
3034
  const isCookie = (object) => {
2931
3035
  return object != null && typeof object.name === "string" && typeof object.isSigned === "boolean" && typeof object.parse === "function" && typeof object.serialize === "function";
@@ -2951,7 +3055,7 @@ function encodeData(value) {
2951
3055
  function decodeData(value) {
2952
3056
  try {
2953
3057
  return JSON.parse(decodeURIComponent(myEscape(atob(value))));
2954
- } catch (e) {
3058
+ } catch {
2955
3059
  return {};
2956
3060
  }
2957
3061
  }
@@ -3015,6 +3119,12 @@ function flash(name) {
3015
3119
  *
3016
3120
  * Note: This function is typically not invoked directly by application code.
3017
3121
  * Instead, use a `SessionStorage` object's `getSession` method.
3122
+ *
3123
+ * @category Utils
3124
+ * @param initialData The initial data for the session.
3125
+ * @param id The identifier for the session. Defaults to an empty string for a
3126
+ * new session.
3127
+ * @returns A new {@link Session} object.
3018
3128
  */
3019
3129
  const createSession = (initialData = {}, id = "") => {
3020
3130
  let map = new Map(Object.entries(initialData));
@@ -3049,9 +3159,15 @@ const createSession = (initialData = {}, id = "") => {
3049
3159
  };
3050
3160
  };
3051
3161
  /**
3052
- * Returns true if an object is a React Router session.
3162
+ * Returns `true` if a value is a React Router {@link Session} object.
3053
3163
  *
3054
- * @see https://reactrouter.com/api/utils/isSession
3164
+ * @public
3165
+ * @category Utils
3166
+ * @mode framework
3167
+ * @mode data
3168
+ * @param object The value to check.
3169
+ * @returns `true` if the value is a React Router {@link Session} object;
3170
+ * otherwise, `false`.
3055
3171
  */
3056
3172
  const isSession = (object) => {
3057
3173
  return object != null && typeof object.id === "string" && typeof object.data !== "undefined" && typeof object.has === "function" && typeof object.get === "function" && typeof object.set === "function" && typeof object.flash === "function" && typeof object.unset === "function";
@@ -3061,6 +3177,11 @@ const isSession = (object) => {
3061
3177
  *
3062
3178
  * Note: This is a low-level API that should only be used if none of the
3063
3179
  * existing session storage options meet your requirements.
3180
+ *
3181
+ * @category Utils
3182
+ * @param strategy The strategy used to store session identifiers and data.
3183
+ * @returns A {@link SessionStorage} object that persists session data using the
3184
+ * provided strategy.
3064
3185
  */
3065
3186
  function createSessionStorage({ cookie: cookieArg, createData, readData, updateData, deleteData }) {
3066
3187
  let cookie = isCookie(cookieArg) ? cookieArg : createCookie(cookieArg?.name || "__session", cookieArg);
@@ -3100,6 +3221,14 @@ function warnOnceAboutSigningSessionCookie(cookie) {
3100
3221
  * needed, and can help to simplify some load-balanced scenarios. However, it
3101
3222
  * also has the limitation that serialized session data may not exceed the
3102
3223
  * browser's maximum cookie size. Trade-offs!
3224
+ *
3225
+ * @public
3226
+ * @category Utils
3227
+ * @mode framework
3228
+ * @mode data
3229
+ * @param options Options for creating the cookie-backed session storage.
3230
+ * @returns A {@link SessionStorage} object that stores all session data in its
3231
+ * cookie.
3103
3232
  */
3104
3233
  function createCookieSessionStorage({ cookie: cookieArg } = {}) {
3105
3234
  let cookie = isCookie(cookieArg) ? cookieArg : createCookie(cookieArg?.name || "__session", cookieArg);
@@ -3125,18 +3254,24 @@ function createCookieSessionStorage({ cookie: cookieArg } = {}) {
3125
3254
  //#endregion
3126
3255
  //#region lib/server-runtime/sessions/memoryStorage.ts
3127
3256
  /**
3128
- * Creates and returns a simple in-memory SessionStorage object, mostly useful
3129
- * for testing and as a reference implementation.
3257
+ * Creates and returns a simple in-memory SessionStorage object.
3258
+ *
3259
+ * Intended for local development and testing. It does not scale beyond a single
3260
+ * process, and all session data is lost when the server process stops/restarts.
3130
3261
  *
3131
- * Note: This storage does not scale beyond a single process, so it is not
3132
- * suitable for most production scenarios.
3262
+ * @public
3263
+ * @category Utils
3264
+ * @mode framework
3265
+ * @mode data
3266
+ * @param options Options for creating the in-memory session storage.
3267
+ * @returns A {@link SessionStorage} object that stores session data in memory.
3133
3268
  */
3134
3269
  function createMemorySessionStorage({ cookie } = {}) {
3135
3270
  let map = /* @__PURE__ */ new Map();
3136
3271
  return createSessionStorage({
3137
3272
  cookie,
3138
3273
  async createData(data, expires) {
3139
- let id = Math.random().toString(36).substring(2, 10);
3274
+ let id = crypto.randomUUID();
3140
3275
  map.set(id, {
3141
3276
  data,
3142
3277
  expires
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.2.0
2
+ * react-router v8.3.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.2.0
2
+ * react-router v8.3.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -12,14 +12,19 @@
12
12
  function throwIfPotentialCSRFAttack(request, allowedActionOrigins) {
13
13
  let originHeader = request.headers.get("origin");
14
14
  let originDomain = null;
15
+ let originUrl = null;
15
16
  try {
16
- originDomain = typeof originHeader === "string" && originHeader !== "null" ? new URL(originHeader).host : originHeader;
17
+ if (typeof originHeader === "string" && originHeader !== "null") {
18
+ originUrl = new URL(originHeader);
19
+ originDomain = originUrl.host;
20
+ } else originDomain = originHeader;
17
21
  } catch {
18
22
  throw new Error(`\`origin\` header is not a valid URL. Aborting the action.`);
19
23
  }
20
- let host = new URL(request.url).host;
21
- if (originDomain && originDomain !== host) {
22
- if (!isAllowedOrigin(originDomain, allowedActionOrigins)) throw new Error("The `request.url` host does not match `origin` header from a forwarded action request. Aborting the action.");
24
+ let requestUrl = new URL(request.url);
25
+ let originMatchesRequest = originUrl ? originUrl.origin === requestUrl.origin : originDomain === requestUrl.host;
26
+ if (originDomain && !originMatchesRequest) {
27
+ if (!isAllowedOrigin(originDomain, allowedActionOrigins)) throw new Error("The `request.url` origin does not match `origin` header from a forwarded action request. Aborting the action.");
23
28
  }
24
29
  }
25
30
  function matchWildcardDomain(domain, pattern) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.2.0
2
+ * react-router v8.3.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -8,8 +8,9 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import { createMemoryHistory, invariant, parsePath, warning } from "./router/history.js";
11
+ import { createMemoryHistory, createPath, invariant, parsePath, warning } from "./router/history.js";
12
12
  import { defaultMapRouteProperties, getResolveToMatches, getRoutePattern, resolveTo, stripBasename } from "./router/utils.js";
13
+ import { getNavigatorCurrentUrl, validateNavigationTarget } from "./router/navigation.js";
13
14
  import { createRouter } from "./router/router.js";
14
15
  import { AwaitContext, DataRouterContext, DataRouterStateContext, FetchersContext, LocationContext, NavigationContext, RouteContext, ViewTransitionContext, useIsRSCRouterContext } from "./context.js";
15
16
  import { _renderMatches, useActionData, useAsyncValue, useInRouterContext, useLoaderData, useLocation, useMatches, useNavigate, useOutlet, useParams, useRouteError, useRoutes, useRoutesImpl } from "./hooks.js";
@@ -256,6 +257,7 @@ function RouterProvider({ router, flushSync: reactDomFlushSyncImpl, onError, use
256
257
  let navigator = React$1.useMemo(() => {
257
258
  return {
258
259
  createHref: router.createHref,
260
+ createURL: router.createURL,
259
261
  encodeLocation: router.encodeLocation,
260
262
  go: (n) => router.navigate(n),
261
263
  push: (to, state, opts) => router.navigate(to, {
@@ -379,12 +381,13 @@ function MemoryRouter({ basename, children, initialEntries, initialIndex, useTra
379
381
  */
380
382
  function Navigate({ to, replace, state, relative }) {
381
383
  invariant(useInRouterContext(), `<Navigate> may be used only in the context of a <Router> component.`);
382
- let { static: isStatic } = React$1.useContext(NavigationContext);
384
+ let { static: isStatic, navigator } = React$1.useContext(NavigationContext);
383
385
  warning(!isStatic, "<Navigate> must not be used on the initial render in a <StaticRouter>. This is a no-op, but you should modify your code so the <Navigate> is only ever rendered in response to some user interaction or state change.");
384
386
  let { matches } = React$1.useContext(RouteContext);
385
387
  let { pathname: locationPathname } = useLocation();
386
388
  let navigate = useNavigate();
387
389
  let path = resolveTo(to, getResolveToMatches(matches), locationPathname, relative === "path");
390
+ validateNavigationTarget(typeof to === "string" ? to : createPath(to), navigator.createHref(path), getNavigatorCurrentUrl(navigator), "reject");
388
391
  let jsonPath = JSON.stringify(path);
389
392
  React$1.useEffect(() => {
390
393
  navigate(JSON.parse(jsonPath), {
@@ -55,6 +55,7 @@ interface NavigateOptions {
55
55
  */
56
56
  interface Navigator {
57
57
  createHref: History["createHref"];
58
+ createURL?: History["createURL"];
58
59
  encodeLocation?: History["encodeLocation"];
59
60
  go: History["go"];
60
61
  push(to: To, state?: any, opts?: NavigateOptions): void;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.2.0
2
+ * react-router v8.3.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -6,30 +6,30 @@ import { RelativeRoutingType } from "../router/router.js";
6
6
  type ParamKeyValuePair = [string, string];
7
7
  type URLSearchParamsInit = string | ParamKeyValuePair[] | Record<string, string | string[]> | URLSearchParams;
8
8
  /**
9
- Creates a URLSearchParams object using the given initializer.
10
-
11
- This is identical to `new URLSearchParams(init)` except it also
12
- supports arrays as values in the object form of the initializer
13
- instead of just strings. This is convenient when you need multiple
14
- values for a given key, but don't want to use an array initializer.
15
-
16
- For example, instead of:
17
-
18
- ```tsx
19
- let searchParams = new URLSearchParams([
20
- ['sort', 'name'],
21
- ['sort', 'price']
22
- ]);
23
- ```
24
- you can do:
25
-
26
- ```
27
- let searchParams = createSearchParams({
28
- sort: ['name', 'price']
29
- });
30
- ```
31
-
32
- @category Utils
9
+ * Creates a URLSearchParams object using the given initializer.
10
+ *
11
+ * This is identical to `new URLSearchParams(init)` except it also supports
12
+ * arrays as values in the object form of the initializer instead of just
13
+ * strings. This is convenient when you need multiple values for a given key,
14
+ * but don't want to use an array initializer.
15
+ *
16
+ * @example
17
+ * // Instead of:
18
+ * let searchParams = new URLSearchParams([
19
+ * ["sort", "name"],
20
+ * ["sort", "price"],
21
+ * ]);
22
+ *
23
+ * // You can do:
24
+ * let searchParams = createSearchParams({
25
+ * sort: ["name", "price"],
26
+ * });
27
+ *
28
+ * @public
29
+ * @category Utils
30
+ * @param init The value used to initialize the URL search parameters.
31
+ * @returns A URLSearchParams object containing the initialized search
32
+ * parameters.
33
33
  */
34
34
  declare function createSearchParams(init?: URLSearchParamsInit): URLSearchParams;
35
35
  type JsonObject = { [Key in string]: JsonValue } & { [Key in string]?: JsonValue | undefined };