react-router 8.3.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 (138) hide show
  1. package/CHANGELOG.md +13 -2
  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 +8 -0
  5. package/dist/development/index-react-server.js +22 -13
  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.js +2 -2
  12. package/dist/development/lib/dom/lib.js +20 -5
  13. package/dist/development/lib/dom/server.js +1 -1
  14. package/dist/development/lib/dom/ssr/components.js +2 -2
  15. package/dist/development/lib/dom/ssr/data.js +1 -1
  16. package/dist/development/lib/dom/ssr/errorBoundaries.js +1 -1
  17. package/dist/development/lib/dom/ssr/fallback.js +1 -1
  18. package/dist/development/lib/dom/ssr/fog-of-war.js +2 -1
  19. package/dist/development/lib/dom/ssr/hydration.js +1 -1
  20. package/dist/development/lib/dom/ssr/invariant.js +1 -1
  21. package/dist/development/lib/dom/ssr/links.js +1 -1
  22. package/dist/development/lib/dom/ssr/markup.js +1 -1
  23. package/dist/development/lib/dom/ssr/routeModules.js +1 -1
  24. package/dist/development/lib/dom/ssr/routes-test-stub.js +1 -1
  25. package/dist/development/lib/dom/ssr/routes.js +1 -1
  26. package/dist/development/lib/dom/ssr/server.js +1 -1
  27. package/dist/development/lib/dom/ssr/single-fetch.js +5 -5
  28. package/dist/development/lib/dom-export/dom-router-provider.js +1 -1
  29. package/dist/development/lib/dom-export/hydrated-router.js +1 -1
  30. package/dist/development/lib/errors.js +1 -1
  31. package/dist/development/lib/hooks.js +6 -3
  32. package/dist/development/lib/href.js +1 -1
  33. package/dist/development/lib/router/history.js +2 -2
  34. package/dist/development/lib/router/instrumentation.js +1 -1
  35. package/dist/development/lib/router/navigation.js +45 -0
  36. package/dist/development/lib/router/router.d.ts +8 -0
  37. package/dist/development/lib/router/router.js +31 -17
  38. package/dist/development/lib/router/url.js +1 -1
  39. package/dist/development/lib/router/utils.js +10 -6
  40. package/dist/development/lib/rsc/browser.js +4 -1
  41. package/dist/development/lib/rsc/errorBoundaries.js +1 -1
  42. package/dist/development/lib/rsc/html-stream/browser.js +1 -1
  43. package/dist/development/lib/rsc/html-stream/server.js +2 -2
  44. package/dist/development/lib/rsc/route-modules.js +1 -1
  45. package/dist/development/lib/rsc/server.ssr.js +2 -2
  46. package/dist/development/lib/server-runtime/cookies.js +2 -2
  47. package/dist/development/lib/server-runtime/crypto.js +2 -2
  48. package/dist/development/lib/server-runtime/data.js +1 -1
  49. package/dist/development/lib/server-runtime/dev.js +2 -2
  50. package/dist/development/lib/server-runtime/entry.js +1 -1
  51. package/dist/development/lib/server-runtime/errors.js +1 -1
  52. package/dist/development/lib/server-runtime/headers.js +1 -1
  53. package/dist/development/lib/server-runtime/invariant.js +1 -1
  54. package/dist/development/lib/server-runtime/mode.js +1 -1
  55. package/dist/development/lib/server-runtime/routeMatching.js +1 -1
  56. package/dist/development/lib/server-runtime/routes.js +1 -1
  57. package/dist/development/lib/server-runtime/server.js +2 -2
  58. package/dist/development/lib/server-runtime/serverHandoff.js +1 -1
  59. package/dist/development/lib/server-runtime/sessions/cookieStorage.js +1 -1
  60. package/dist/development/lib/server-runtime/sessions/memoryStorage.js +1 -1
  61. package/dist/development/lib/server-runtime/sessions.js +1 -1
  62. package/dist/development/lib/server-runtime/single-fetch.js +2 -2
  63. package/dist/development/lib/server-runtime/urls.js +1 -1
  64. package/dist/development/lib/server-runtime/warnings.js +1 -1
  65. package/dist/development/lib/types/internal.js +1 -1
  66. package/dist/development/vendor/turbo-stream-v2/flatten.js +1 -1
  67. package/dist/development/vendor/turbo-stream-v2/turbo-stream.js +4 -4
  68. package/dist/development/vendor/turbo-stream-v2/unflatten.js +1 -1
  69. package/dist/development/vendor/turbo-stream-v2/utils.js +1 -1
  70. package/dist/production/dom-export.js +1 -1
  71. package/dist/production/index-react-server-client.js +1 -1
  72. package/dist/production/index-react-server.d.ts +8 -0
  73. package/dist/production/index-react-server.js +22 -13
  74. package/dist/production/index.js +1 -1
  75. package/dist/production/lib/actions.js +10 -5
  76. package/dist/production/lib/components.js +6 -3
  77. package/dist/production/lib/context.d.ts +1 -0
  78. package/dist/production/lib/context.js +1 -1
  79. package/dist/production/lib/dom/dom.js +2 -2
  80. package/dist/production/lib/dom/lib.js +20 -5
  81. package/dist/production/lib/dom/server.js +1 -1
  82. package/dist/production/lib/dom/ssr/components.js +2 -2
  83. package/dist/production/lib/dom/ssr/data.js +1 -1
  84. package/dist/production/lib/dom/ssr/errorBoundaries.js +1 -1
  85. package/dist/production/lib/dom/ssr/fallback.js +1 -1
  86. package/dist/production/lib/dom/ssr/fog-of-war.js +2 -1
  87. package/dist/production/lib/dom/ssr/hydration.js +1 -1
  88. package/dist/production/lib/dom/ssr/invariant.js +1 -1
  89. package/dist/production/lib/dom/ssr/links.js +1 -1
  90. package/dist/production/lib/dom/ssr/markup.js +1 -1
  91. package/dist/production/lib/dom/ssr/routeModules.js +1 -1
  92. package/dist/production/lib/dom/ssr/routes-test-stub.js +1 -1
  93. package/dist/production/lib/dom/ssr/routes.js +1 -1
  94. package/dist/production/lib/dom/ssr/server.js +1 -1
  95. package/dist/production/lib/dom/ssr/single-fetch.js +5 -5
  96. package/dist/production/lib/dom-export/dom-router-provider.js +1 -1
  97. package/dist/production/lib/dom-export/hydrated-router.js +1 -1
  98. package/dist/production/lib/errors.js +1 -1
  99. package/dist/production/lib/hooks.js +6 -3
  100. package/dist/production/lib/href.js +1 -1
  101. package/dist/production/lib/router/history.js +2 -2
  102. package/dist/production/lib/router/instrumentation.js +1 -1
  103. package/dist/production/lib/router/navigation.js +45 -0
  104. package/dist/production/lib/router/router.d.ts +8 -0
  105. package/dist/production/lib/router/router.js +31 -17
  106. package/dist/production/lib/router/url.js +1 -1
  107. package/dist/production/lib/router/utils.js +10 -6
  108. package/dist/production/lib/rsc/browser.js +4 -1
  109. package/dist/production/lib/rsc/errorBoundaries.js +1 -1
  110. package/dist/production/lib/rsc/html-stream/browser.js +1 -1
  111. package/dist/production/lib/rsc/html-stream/server.js +2 -2
  112. package/dist/production/lib/rsc/route-modules.js +1 -1
  113. package/dist/production/lib/rsc/server.ssr.js +2 -2
  114. package/dist/production/lib/server-runtime/cookies.js +2 -2
  115. package/dist/production/lib/server-runtime/crypto.js +2 -2
  116. package/dist/production/lib/server-runtime/data.js +1 -1
  117. package/dist/production/lib/server-runtime/dev.js +2 -2
  118. package/dist/production/lib/server-runtime/entry.js +1 -1
  119. package/dist/production/lib/server-runtime/errors.js +1 -1
  120. package/dist/production/lib/server-runtime/headers.js +1 -1
  121. package/dist/production/lib/server-runtime/invariant.js +1 -1
  122. package/dist/production/lib/server-runtime/mode.js +1 -1
  123. package/dist/production/lib/server-runtime/routeMatching.js +1 -1
  124. package/dist/production/lib/server-runtime/routes.js +1 -1
  125. package/dist/production/lib/server-runtime/server.js +2 -2
  126. package/dist/production/lib/server-runtime/serverHandoff.js +1 -1
  127. package/dist/production/lib/server-runtime/sessions/cookieStorage.js +1 -1
  128. package/dist/production/lib/server-runtime/sessions/memoryStorage.js +1 -1
  129. package/dist/production/lib/server-runtime/sessions.js +1 -1
  130. package/dist/production/lib/server-runtime/single-fetch.js +2 -2
  131. package/dist/production/lib/server-runtime/urls.js +1 -1
  132. package/dist/production/lib/server-runtime/warnings.js +1 -1
  133. package/dist/production/lib/types/internal.js +1 -1
  134. package/dist/production/vendor/turbo-stream-v2/flatten.js +1 -1
  135. package/dist/production/vendor/turbo-stream-v2/turbo-stream.js +4 -4
  136. package/dist/production/vendor/turbo-stream-v2/unflatten.js +1 -1
  137. package/dist/production/vendor/turbo-stream-v2/utils.js +1 -1
  138. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.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() {
@@ -506,13 +506,13 @@ function matchPathImpl(pattern, pathname, matcher, compiledParams) {
506
506
  let match = pathname.match(matcher);
507
507
  if (!match) return null;
508
508
  let matchedPathname = match[0];
509
- let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
509
+ let pathnameBase = removeTrailingSlash(matchedPathname, 1);
510
510
  let captureGroups = match.slice(1);
511
511
  return {
512
512
  params: compiledParams.reduce((memo, { paramName, isOptional }, index) => {
513
513
  if (paramName === "*") {
514
514
  let splatValue = captureGroups[index] || "";
515
- pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
515
+ pathnameBase = removeTrailingSlash(matchedPathname.slice(0, matchedPathname.length - splatValue.length), 1);
516
516
  }
517
517
  const value = captureGroups[index];
518
518
  if (isOptional && !value) memo[paramName] = void 0;
@@ -581,7 +581,7 @@ function resolvePath(to, fromPathname = "/") {
581
581
  let pathname;
582
582
  if (toPathname) {
583
583
  toPathname = removeDoubleSlashes(toPathname);
584
- if (toPathname.startsWith("/")) pathname = resolvePathname(toPathname.substring(1), "/");
584
+ if (toPathname.startsWith("/") || toPathname.startsWith("\\")) pathname = resolvePathname(toPathname.substring(1), "/");
585
585
  else pathname = resolvePathname(toPathname, fromPathname);
586
586
  } else pathname = fromPathname;
587
587
  return {
@@ -642,7 +642,11 @@ function resolveTo(toArg, routePathnames, locationPathname, isPathRelative = fal
642
642
  }
643
643
  const removeDoubleSlashes = (path) => path.replace(/[\\/]{2,}/g, "/");
644
644
  const joinPaths = (paths) => removeDoubleSlashes(paths.join("/"));
645
- const removeTrailingSlash = (path) => path.replace(/\/+$/, "");
645
+ function removeTrailingSlash(path, minLength = 0) {
646
+ let end = path.length;
647
+ while (end > minLength && path.charCodeAt(end - 1) === 47) end--;
648
+ return end === path.length ? path : path.slice(0, end);
649
+ }
646
650
  const normalizePathname = (pathname) => removeTrailingSlash(pathname).replace(/^\/*/, "/");
647
651
  const normalizeSearch = (search) => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search;
648
652
  const normalizeHash = (hash) => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash;
@@ -1807,7 +1811,7 @@ async function callDataStrategyImpl(dataStrategyImpl, request, path, matches, fe
1807
1811
  });
1808
1812
  try {
1809
1813
  await Promise.all(matches.flatMap((m) => [m._lazyPromises?.handler, m._lazyPromises?.route]));
1810
- } catch (e) {}
1814
+ } catch {}
1811
1815
  return results;
1812
1816
  }
1813
1817
  async function callLoaderOrAction({ request, path, pattern, match, lazyHandlerPromise, lazyRoutePromise, handlerOverride, scopedContext }) {
@@ -2208,14 +2212,19 @@ function getPathsWithAncestors(paths) {
2208
2212
  function throwIfPotentialCSRFAttack(request, allowedActionOrigins) {
2209
2213
  let originHeader = request.headers.get("origin");
2210
2214
  let originDomain = null;
2215
+ let originUrl = null;
2211
2216
  try {
2212
- originDomain = typeof originHeader === "string" && originHeader !== "null" ? new URL(originHeader).host : originHeader;
2217
+ if (typeof originHeader === "string" && originHeader !== "null") {
2218
+ originUrl = new URL(originHeader);
2219
+ originDomain = originUrl.host;
2220
+ } else originDomain = originHeader;
2213
2221
  } catch {
2214
2222
  throw new Error(`\`origin\` header is not a valid URL. Aborting the action.`);
2215
2223
  }
2216
- let host = new URL(request.url).host;
2217
- if (originDomain && originDomain !== host) {
2218
- if (!isAllowedOrigin(originDomain, allowedActionOrigins)) throw new Error("The `request.url` host does not match `origin` header from a forwarded action request. Aborting the action.");
2224
+ let requestUrl = new URL(request.url);
2225
+ let originMatchesRequest = originUrl ? originUrl.origin === requestUrl.origin : originDomain === requestUrl.host;
2226
+ if (originDomain && !originMatchesRequest) {
2227
+ if (!isAllowedOrigin(originDomain, allowedActionOrigins)) throw new Error("The `request.url` origin does not match `origin` header from a forwarded action request. Aborting the action.");
2219
2228
  }
2220
2229
  }
2221
2230
  function matchWildcardDomain(domain, pattern) {
@@ -2939,7 +2948,7 @@ const unsign = async (cookie, secret) => {
2939
2948
  try {
2940
2949
  let signature = byteStringToUint8Array(atob(hash));
2941
2950
  return await crypto.subtle.verify("HMAC", key, signature, data) ? value : false;
2942
- } catch (e) {
2951
+ } catch {
2943
2952
  return false;
2944
2953
  }
2945
2954
  };
@@ -3037,7 +3046,7 @@ function encodeData(value) {
3037
3046
  function decodeData(value) {
3038
3047
  try {
3039
3048
  return JSON.parse(decodeURIComponent(myEscape(atob(value))));
3040
- } catch (e) {
3049
+ } catch {
3041
3050
  return {};
3042
3051
  }
3043
3052
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.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.3.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.3.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.3.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.3.0
2
+ * react-router v8.3.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -75,7 +75,7 @@ function isFormDataSubmitterSupported() {
75
75
  if (_formDataSupportsSubmitter === null) try {
76
76
  new FormData(document.createElement("form"), 0);
77
77
  _formDataSupportsSubmitter = false;
78
- } catch (e) {
78
+ } catch {
79
79
  _formDataSupportsSubmitter = true;
80
80
  }
81
81
  return _formDataSupportsSubmitter;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.0
2
+ * react-router v8.3.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -22,8 +22,8 @@ import * as React$1 from "react";
22
22
  //#region lib/dom/lib.tsx
23
23
  const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
24
24
  try {
25
- if (isBrowser) window.__reactRouterVersion = "8.3.0";
26
- } catch (e) {}
25
+ if (isBrowser) window.__reactRouterVersion = "8";
26
+ } catch {}
27
27
  /**
28
28
  * Create a new {@link DataRouter| data router} that manages the application
29
29
  * path via [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState)
@@ -123,7 +123,7 @@ function deserializeErrors(errors) {
123
123
  let error = new ErrorConstructor(val.message);
124
124
  error.stack = "";
125
125
  serialized[key] = error;
126
- } catch (e) {}
126
+ } catch {}
127
127
  }
128
128
  if (serialized[key] == null) {
129
129
  let error = new Error(val.message);
@@ -796,6 +796,7 @@ function useSubmit() {
796
796
  if (options.navigate === false) await routerFetch(options.fetcherKey || getUniqueFetcherId(), currentRouteId, options.action || action, {
797
797
  defaultShouldRevalidate: options.defaultShouldRevalidate,
798
798
  preventScrollReset: options.preventScrollReset,
799
+ relative: options.relative,
799
800
  formData,
800
801
  body,
801
802
  formMethod: options.method || method,
@@ -805,6 +806,7 @@ function useSubmit() {
805
806
  else await routerNavigate(options.action || action, {
806
807
  defaultShouldRevalidate: options.defaultShouldRevalidate,
807
808
  preventScrollReset: options.preventScrollReset,
809
+ relative: options.relative,
808
810
  formData,
809
811
  body,
810
812
  formMethod: options.method || method,
@@ -1087,6 +1089,9 @@ function useScrollRestoration({ getKey, storageKey } = {}) {
1087
1089
  window.history.scrollRestoration = "auto";
1088
1090
  };
1089
1091
  }, []);
1092
+ usePageShow(React$1.useCallback((event) => {
1093
+ if (event.persisted) window.history.scrollRestoration = "manual";
1094
+ }, []));
1090
1095
  usePageHide(React$1.useCallback(() => {
1091
1096
  if (navigation.state === "idle") {
1092
1097
  let key = getScrollRestorationKey(location, matches, basename, getKey);
@@ -1111,7 +1116,7 @@ function useScrollRestoration({ getKey, storageKey } = {}) {
1111
1116
  try {
1112
1117
  let sessionPositions = sessionStorage.getItem(storageKey || SCROLL_RESTORATION_STORAGE_KEY);
1113
1118
  if (sessionPositions) savedScrollPositions = JSON.parse(sessionPositions);
1114
- } catch (e) {}
1119
+ } catch {}
1115
1120
  }, [storageKey]);
1116
1121
  React$1.useLayoutEffect(() => {
1117
1122
  let disableScrollRestoration = router?.enableScrollRestoration(savedScrollPositions, () => window.scrollY, getKey ? (location, matches) => getScrollRestorationKey(location, matches, basename, getKey) : void 0);
@@ -1179,6 +1184,16 @@ function usePageHide(callback, options) {
1179
1184
  };
1180
1185
  }, [callback, capture]);
1181
1186
  }
1187
+ function usePageShow(callback, options) {
1188
+ let { capture } = options || {};
1189
+ React$1.useEffect(() => {
1190
+ let opts = capture != null ? { capture } : void 0;
1191
+ window.addEventListener("pageshow", callback, opts);
1192
+ return () => {
1193
+ window.removeEventListener("pageshow", callback, opts);
1194
+ };
1195
+ }, [callback, capture]);
1196
+ }
1182
1197
  /**
1183
1198
  * Wrapper around {@link useBlocker} to show a [`window.confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm)
1184
1199
  * prompt to users instead of building a custom UI with {@link useBlocker}.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.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.3.0
2
+ * react-router v8.3.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -425,7 +425,7 @@ function Meta() {
425
425
  type: "application/ld+json",
426
426
  dangerouslySetInnerHTML: { __html: escapeHtml(json) }
427
427
  });
428
- } catch (e) {
428
+ } catch {
429
429
  return null;
430
430
  }
431
431
  return /* @__PURE__ */ React$1.createElement("meta", {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.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.3.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.3.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.3.0
2
+ * react-router v8.3.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -154,6 +154,7 @@ async function fetchAndApplyManifestPatches(paths, errorReloadPath, manifest, ro
154
154
  if (signal?.aborted) return;
155
155
  throw e;
156
156
  }
157
+ if (signal?.aborted) return;
157
158
  let knownRoutes = new Set(Object.keys(manifest.routes));
158
159
  let patches = Object.values(serverPatches).reduce((acc, route) => {
159
160
  if (route && !knownRoutes.has(route.id)) acc[route.id] = route;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.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.3.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.3.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.3.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.3.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.3.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.3.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.3.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.3.0
2
+ * react-router v8.3.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -205,7 +205,7 @@ async function bubbleMiddlewareErrors(singleFetchPromise, matches, routesParams,
205
205
  if (middlewareError !== void 0) Array.from(routesParams.values()).forEach((routeId) => {
206
206
  if (results[routeId].result instanceof SingleFetchNoResultError) results[routeId].result = middlewareError;
207
207
  });
208
- } catch (e) {}
208
+ } catch {}
209
209
  }
210
210
  async function singleFetchLoaderFetcherStrategy(args, fetchAndDecode) {
211
211
  let fetcherMatch = args.matches.find((m) => m.shouldCallHandler());
@@ -277,7 +277,7 @@ async function fetchAndDecodeViaTurboStream(args, targetRoutes) {
277
277
  status: res.status,
278
278
  data
279
279
  };
280
- } catch (e) {
280
+ } catch {
281
281
  throw new Error("Unable to decode turbo-stream response");
282
282
  }
283
283
  }
@@ -326,13 +326,13 @@ function createDeferred() {
326
326
  res(val);
327
327
  try {
328
328
  await promise;
329
- } catch (e) {}
329
+ } catch {}
330
330
  };
331
331
  reject = async (error) => {
332
332
  rej(error);
333
333
  try {
334
334
  await promise;
335
- } catch (e) {}
335
+ } catch {}
336
336
  };
337
337
  });
338
338
  return {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.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.3.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.3.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.3.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 { invariant, parsePath, warning } from "./router/history.js";
11
+ import { createPath, invariant, parsePath, warning } from "./router/history.js";
12
12
  import { convertRouteMatchToUiMatch, decodePath, getResolveToMatches, getRoutePattern, isBrowser, isRouteErrorResponse, joinPaths, matchPath, matchRoutes, parseToInfo, resolveTo, stripBasename } from "./router/utils.js";
13
+ import { getNavigatorCurrentUrl, validateNavigationTarget } from "./router/navigation.js";
13
14
  import { IDLE_BLOCKER, hasInvalidProtocol } from "./router/router.js";
14
15
  import { AwaitContext, DataRouterContext, DataRouterStateContext, LocationContext, NavigationContext, RSCRouterContext, RouteContext, RouteErrorContext } from "./context.js";
15
16
  import { decodeRedirectErrorDigest, decodeRouteErrorResponseDigest } from "./errors.js";
@@ -291,6 +292,7 @@ function useNavigateUnstable() {
291
292
  }
292
293
  let path = resolveTo(to, JSON.parse(routePathnamesJson), locationPathname, options.relative === "path");
293
294
  if (dataRouterContext == null && basename !== "/") path.pathname = path.pathname === "/" ? basename : joinPaths([basename, path.pathname]);
295
+ validateNavigationTarget(typeof to === "string" ? to : createPath(to), navigator.createHref(path), getNavigatorCurrentUrl(navigator), "reject");
294
296
  (!!options.replace ? navigator.replace : navigator.push)(path, options.state, options);
295
297
  }, [
296
298
  basename,
@@ -672,7 +674,7 @@ var RenderErrorBoundary = class extends React$1.Component {
672
674
  };
673
675
  const errorRedirectHandledMap = /* @__PURE__ */ new WeakMap();
674
676
  function RSCErrorHandler({ children, error }) {
675
- let { basename } = React$1.useContext(NavigationContext);
677
+ let { basename, navigator } = React$1.useContext(NavigationContext);
676
678
  if (typeof error === "object" && error && "digest" in error && typeof error.digest === "string") {
677
679
  let redirect = decodeRedirectErrorDigest(error.digest);
678
680
  if (redirect) {
@@ -680,6 +682,7 @@ function RSCErrorHandler({ children, error }) {
680
682
  if (existingRedirect) throw existingRedirect;
681
683
  let parsed = parseToInfo(redirect.location, basename);
682
684
  let target = parsed.absoluteURL || parsed.to;
685
+ validateNavigationTarget(redirect.location, target, getNavigatorCurrentUrl(navigator), "allow-explicit");
683
686
  if (hasInvalidProtocol(target)) throw new Error("Invalid redirect location");
684
687
  if (isBrowser && !errorRedirectHandledMap.get(error)) if (parsed.isExternal || redirect.reloadDocument) window.location.href = target;
685
688
  else {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.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.3.0
2
+ * react-router v8.3.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -193,7 +193,7 @@ function warning(cond, message) {
193
193
  if (typeof console !== "undefined") console.warn(message);
194
194
  try {
195
195
  throw new Error(message);
196
- } catch (e) {}
196
+ } catch {}
197
197
  }
198
198
  }
199
199
  function createKey() {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.0
2
+ * react-router v8.3.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -0,0 +1,45 @@
1
+ /**
2
+ * react-router v8.3.1
3
+ *
4
+ * Copyright (c) Remix Software Inc.
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE.md file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ //#region lib/router/navigation.ts
12
+ const DEFAULT_NAVIGATION_URL = new URL("http://localhost");
13
+ function getNavigatorCurrentUrl(navigator) {
14
+ if (navigator.createURL) return navigator.createURL("/");
15
+ try {
16
+ return new URL(navigator.createHref("/"), DEFAULT_NAVIGATION_URL);
17
+ } catch {
18
+ return DEFAULT_NAVIGATION_URL;
19
+ }
20
+ }
21
+ function isSameOrigin(a, b) {
22
+ return a.origin === b.origin && (a.origin !== "null" || a.protocol === b.protocol && a.host === b.host);
23
+ }
24
+ function isExplicitUrl(destination, target) {
25
+ if (destination.startsWith("//")) return true;
26
+ let protocol = target.protocol.toLowerCase();
27
+ if (!destination.toLowerCase().startsWith(protocol)) return false;
28
+ return target.host === "" || destination.slice(protocol.length).startsWith("//");
29
+ }
30
+ function validateNavigationTarget(original, resolved, currentUrl, externalPolicy) {
31
+ let originalUrl = null;
32
+ try {
33
+ originalUrl = original == null ? null : new URL(original, currentUrl);
34
+ } catch {}
35
+ let resolvedUrl = new URL(resolved, currentUrl);
36
+ let originalIsExternal = originalUrl != null && !isSameOrigin(originalUrl, currentUrl);
37
+ let resolvedIsExternal = !isSameOrigin(resolvedUrl, currentUrl);
38
+ if (externalPolicy === "reject") {
39
+ if (originalIsExternal || resolvedIsExternal) throw new Error("External navigation is not allowed");
40
+ } else if (resolvedIsExternal) {
41
+ if (originalUrl == null || !isExplicitUrl(original, originalUrl) || !isSameOrigin(originalUrl, resolvedUrl)) throw new Error("External navigation is not allowed");
42
+ }
43
+ }
44
+ //#endregion
45
+ export { getNavigatorCurrentUrl, validateNavigationTarget };
@@ -128,6 +128,14 @@ interface Router {
128
128
  * @param location
129
129
  */
130
130
  createHref(location: Location | URL): string;
131
+ /**
132
+ * @private
133
+ * PRIVATE - DO NOT USE
134
+ *
135
+ * Utility function to create a URL for the given location
136
+ * @param location
137
+ */
138
+ createURL?(to: To): URL;
131
139
  /**
132
140
  * @private
133
141
  * PRIVATE - DO NOT USE