sibujs 1.2.0 → 1.4.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 (95) hide show
  1. package/README.md +29 -25
  2. package/dist/browser.cjs +804 -2
  3. package/dist/browser.d.cts +591 -1
  4. package/dist/browser.d.ts +591 -1
  5. package/dist/browser.js +50 -8
  6. package/dist/build.cjs +655 -237
  7. package/dist/build.js +15 -93
  8. package/dist/cdn.global.js +188 -7
  9. package/dist/chunk-2BYQDGN3.js +742 -0
  10. package/dist/chunk-32DY64NT.js +282 -0
  11. package/dist/chunk-3AIRKM3B.js +1263 -0
  12. package/dist/chunk-3X2YG6YM.js +505 -0
  13. package/dist/chunk-5X6PP2UK.js +28 -0
  14. package/dist/chunk-77L6NL3X.js +1097 -0
  15. package/dist/chunk-BGN5ZMP4.js +26 -0
  16. package/dist/chunk-BTU3TJDS.js +365 -0
  17. package/dist/chunk-CHF5OHIA.js +61 -0
  18. package/dist/chunk-CMBFNA7L.js +27 -0
  19. package/dist/chunk-CNZ35WI2.js +178 -0
  20. package/dist/chunk-DAHRH4ON.js +331 -0
  21. package/dist/chunk-EBGIRKQY.js +616 -0
  22. package/dist/chunk-EUZND3CB.js +27 -0
  23. package/dist/chunk-F3FA4F32.js +292 -0
  24. package/dist/chunk-JAKHTMQU.js +1000 -0
  25. package/dist/chunk-JCI5M6U6.js +956 -0
  26. package/dist/chunk-KQPDEVVS.js +398 -0
  27. package/dist/chunk-M4NLBH4I.js +725 -0
  28. package/dist/chunk-NEKUBFPT.js +60 -0
  29. package/dist/chunk-NYVAC6P5.js +37 -0
  30. package/dist/chunk-PTQJDMRT.js +146 -0
  31. package/dist/chunk-QWZG56ET.js +2744 -0
  32. package/dist/chunk-TSOKIX5Z.js +654 -0
  33. package/dist/chunk-UHNL42EF.js +2730 -0
  34. package/dist/chunk-VRW3FULF.js +725 -0
  35. package/dist/chunk-WZSPOOER.js +84 -0
  36. package/dist/chunk-YT6HQ6AM.js +14 -0
  37. package/dist/chunk-ZD6OAMTH.js +277 -0
  38. package/dist/chunk-ZWKZCBO6.js +317 -0
  39. package/dist/contracts-DDrwxvJ-.d.cts +245 -0
  40. package/dist/contracts-DDrwxvJ-.d.ts +245 -0
  41. package/dist/contracts-xo5ckdRP.d.cts +240 -0
  42. package/dist/contracts-xo5ckdRP.d.ts +240 -0
  43. package/dist/data.cjs +35 -2
  44. package/dist/data.d.cts +7 -0
  45. package/dist/data.d.ts +7 -0
  46. package/dist/data.js +9 -8
  47. package/dist/devtools.cjs +122 -0
  48. package/dist/devtools.d.cts +69 -461
  49. package/dist/devtools.d.ts +69 -461
  50. package/dist/devtools.js +127 -6
  51. package/dist/ecosystem.cjs +23 -6
  52. package/dist/ecosystem.d.cts +1 -1
  53. package/dist/ecosystem.d.ts +1 -1
  54. package/dist/ecosystem.js +10 -9
  55. package/dist/extras.cjs +1208 -88
  56. package/dist/extras.d.cts +6 -6
  57. package/dist/extras.d.ts +6 -6
  58. package/dist/extras.js +70 -33
  59. package/dist/index.cjs +663 -158
  60. package/dist/index.d.cts +398 -40
  61. package/dist/index.d.ts +398 -40
  62. package/dist/index.js +39 -21
  63. package/dist/introspect-BumjnBKr.d.cts +477 -0
  64. package/dist/introspect-CZrlcaYy.d.ts +477 -0
  65. package/dist/introspect-Cb0zgpi2.d.cts +477 -0
  66. package/dist/introspect-Y2xNXGSf.d.ts +477 -0
  67. package/dist/motion.js +4 -4
  68. package/dist/patterns.cjs +51 -24
  69. package/dist/patterns.d.cts +19 -57
  70. package/dist/patterns.d.ts +19 -57
  71. package/dist/patterns.js +8 -16
  72. package/dist/performance.js +4 -4
  73. package/dist/plugins.cjs +429 -82
  74. package/dist/plugins.d.cts +27 -4
  75. package/dist/plugins.d.ts +27 -4
  76. package/dist/plugins.js +156 -37
  77. package/dist/ssr-4PBXAOO3.js +40 -0
  78. package/dist/ssr-Do_SiVoL.d.cts +201 -0
  79. package/dist/ssr-Do_SiVoL.d.ts +201 -0
  80. package/dist/ssr.cjs +312 -60
  81. package/dist/ssr.d.cts +10 -1
  82. package/dist/ssr.d.ts +10 -1
  83. package/dist/ssr.js +13 -10
  84. package/dist/tagFactory-DaJ0YWX6.d.cts +47 -0
  85. package/dist/tagFactory-DaJ0YWX6.d.ts +47 -0
  86. package/dist/testing.cjs +233 -2
  87. package/dist/testing.d.cts +42 -1
  88. package/dist/testing.d.ts +42 -1
  89. package/dist/testing.js +129 -2
  90. package/dist/ui.cjs +374 -8
  91. package/dist/ui.d.cts +252 -2
  92. package/dist/ui.d.ts +252 -2
  93. package/dist/ui.js +329 -11
  94. package/dist/widgets.js +7 -7
  95. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { T as TrustedHTML } from './ssr-BA6sxxUd.cjs';
1
+ import { T as TrustedHTML } from './ssr-Do_SiVoL.cjs';
2
2
  export { P as PluginContext, S as SibuPlugin, c as createPlugin, i as inject, p as plugin, r as resetPlugins, t as triggerPluginError, a as triggerPluginMount, b as triggerPluginUnmount } from './plugin-Bek4RhJY.cjs';
3
3
  export { M as Migration, S as SemVer, V as VERSION, b as bundlerMetadata, c as checkCompatibility, a as compareSemVer, d as createBootSequence, e as createBundle, f as createMigrationRunner, g as createModuleRegistry, h as createSSRCache, i as createTestHarness, j as deferNonCritical, k as env, l as healthCheck, m as lazyModule, p as packageInfo, n as parseSemVer, o as preloadCritical, q as prerenderRoutes, s as satisfies } from './startup-0Qv6aosO.cjs';
4
4
 
@@ -74,7 +74,19 @@ interface AsyncRoute extends RouteBase {
74
74
  interface RedirectRoute extends RouteBase {
75
75
  readonly redirect: string | ((to: RouteContext) => string);
76
76
  }
77
- type RouteDef = ComponentRoute | AsyncRoute | RedirectRoute;
77
+ /**
78
+ * A route whose component is loaded on first visit via a dynamic
79
+ * `import()`. This is the ergonomic shorthand for
80
+ * `{ component: lazy(() => import("./Page")) }`.
81
+ *
82
+ * The loader must return a module with a `default` Component export.
83
+ */
84
+ interface LazyRoute extends RouteBase {
85
+ readonly lazy: () => Promise<{
86
+ default: Component;
87
+ }>;
88
+ }
89
+ type RouteDef = ComponentRoute | AsyncRoute | RedirectRoute | LazyRoute;
78
90
  interface RouterOptions {
79
91
  readonly mode?: "history" | "hash";
80
92
  readonly base?: string;
@@ -381,6 +393,11 @@ declare function renderRouteToString(url: string, routes: SSRRouteDef[], _option
381
393
  /**
382
394
  * Generate the full HTML document for a route including serialized state.
383
395
  * Uses renderToDocument pattern with embedded route state.
396
+ *
397
+ * Security: meta/link attribute names are validated against
398
+ * `SAFE_ATTR_NAME`, URL attributes are routed through `sanitizeUrl`,
399
+ * `title` is HTML-escaped, and the embedded state script escapes
400
+ * `U+2028` / `U+2029` plus the usual `<`/`>`/`&` trio.
384
401
  */
385
402
  declare function renderRouteToDocument(url: string, routes: SSRRouteDef[], options?: {
386
403
  title?: string;
@@ -388,12 +405,17 @@ declare function renderRouteToDocument(url: string, routes: SSRRouteDef[], optio
388
405
  links?: Record<string, string>[];
389
406
  scripts?: string[];
390
407
  headExtra?: TrustedHTML;
408
+ nonce?: string;
391
409
  }): string;
392
410
  /**
393
411
  * Serialize route state for embedding in HTML.
394
412
  * Uses a specific key (__SIBU_ROUTE_STATE__) distinct from the generic SSR data key.
413
+ *
414
+ * Security: escapes `<`/`>`/`&` and the ES line-terminator pairs
415
+ * `U+2028` / `U+2029`. Supports an optional `nonce` attribute for
416
+ * strict-CSP compatibility.
395
417
  */
396
- declare function serializeRouteState(state: SSRRouteState): string;
418
+ declare function serializeRouteState(state: SSRRouteState, nonce?: string): string;
397
419
  /**
398
420
  * Deserialize route state on the client from server-embedded data.
399
421
  * Reads from window.__SIBU_ROUTE_STATE__.
@@ -430,7 +452,8 @@ declare function createSSRRouter(routes: SSRRouteDef[]): {
430
452
  links?: Record<string, string>[];
431
453
  scripts?: string[];
432
454
  headExtra?: TrustedHTML;
455
+ nonce?: string;
433
456
  }) => string;
434
457
  };
435
458
 
436
- export { type AsyncComponent, type AsyncRoute, type Component, type ComponentRoute, type Guard, type GuardResult, KeepAliveRoute, type LazyComponent, type NavigationFailure, type NavigationGuard, type NavigationNext, type NavigationResult, type NavigationTarget, Outlet, type Params, type RedirectRoute, Route, type RouteBase, type RouteContext, type RouteDef, type RouteMeta, type RouteTransitionOptions, RouterLink, type RouterOptions, type RouterPlugin, type SSRRouteDef, type SSRRouteState, type ScrollBehavior, type ScrollPosition, SibuRouter, Suspense, Trans, addRoute, afterEach, back, beforeEach, beforeResolve, buildURL, createMemoryRouter, createRouter, createSSRRouter, deserializeRouteState, destroyRouter, forward, getAvailableLocales, getLocale, getRouteInfo, getRouteTransition, go, hasRoute, hasTranslation, hydrateRouter, lazy, navigate, preloadRoute, push, registerTranslations, removeRoute, renderRouteToDocument, renderRouteToString, replace, resolveServerRoute, route, router, routerPlugin, routerState, serializeRouteState, setLocale, setRouteTransition, setRoutes, t };
459
+ export { type AsyncComponent, type AsyncRoute, type Component, type ComponentRoute, type Guard, type GuardResult, KeepAliveRoute, type LazyComponent, type LazyRoute, type NavigationFailure, type NavigationGuard, type NavigationNext, type NavigationResult, type NavigationTarget, Outlet, type Params, type RedirectRoute, Route, type RouteBase, type RouteContext, type RouteDef, type RouteMeta, type RouteTransitionOptions, RouterLink, type RouterOptions, type RouterPlugin, type SSRRouteDef, type SSRRouteState, type ScrollBehavior, type ScrollPosition, SibuRouter, Suspense, Trans, addRoute, afterEach, back, beforeEach, beforeResolve, buildURL, createMemoryRouter, createRouter, createSSRRouter, deserializeRouteState, destroyRouter, forward, getAvailableLocales, getLocale, getRouteInfo, getRouteTransition, go, hasRoute, hasTranslation, hydrateRouter, lazy, navigate, preloadRoute, push, registerTranslations, removeRoute, renderRouteToDocument, renderRouteToString, replace, resolveServerRoute, route, router, routerPlugin, routerState, serializeRouteState, setLocale, setRouteTransition, setRoutes, t };
package/dist/plugins.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { T as TrustedHTML } from './ssr-BA6sxxUd.js';
1
+ import { T as TrustedHTML } from './ssr-Do_SiVoL.js';
2
2
  export { P as PluginContext, S as SibuPlugin, c as createPlugin, i as inject, p as plugin, r as resetPlugins, t as triggerPluginError, a as triggerPluginMount, b as triggerPluginUnmount } from './plugin-Bek4RhJY.js';
3
3
  export { M as Migration, S as SemVer, V as VERSION, b as bundlerMetadata, c as checkCompatibility, a as compareSemVer, d as createBootSequence, e as createBundle, f as createMigrationRunner, g as createModuleRegistry, h as createSSRCache, i as createTestHarness, j as deferNonCritical, k as env, l as healthCheck, m as lazyModule, p as packageInfo, n as parseSemVer, o as preloadCritical, q as prerenderRoutes, s as satisfies } from './startup-0Qv6aosO.js';
4
4
 
@@ -74,7 +74,19 @@ interface AsyncRoute extends RouteBase {
74
74
  interface RedirectRoute extends RouteBase {
75
75
  readonly redirect: string | ((to: RouteContext) => string);
76
76
  }
77
- type RouteDef = ComponentRoute | AsyncRoute | RedirectRoute;
77
+ /**
78
+ * A route whose component is loaded on first visit via a dynamic
79
+ * `import()`. This is the ergonomic shorthand for
80
+ * `{ component: lazy(() => import("./Page")) }`.
81
+ *
82
+ * The loader must return a module with a `default` Component export.
83
+ */
84
+ interface LazyRoute extends RouteBase {
85
+ readonly lazy: () => Promise<{
86
+ default: Component;
87
+ }>;
88
+ }
89
+ type RouteDef = ComponentRoute | AsyncRoute | RedirectRoute | LazyRoute;
78
90
  interface RouterOptions {
79
91
  readonly mode?: "history" | "hash";
80
92
  readonly base?: string;
@@ -381,6 +393,11 @@ declare function renderRouteToString(url: string, routes: SSRRouteDef[], _option
381
393
  /**
382
394
  * Generate the full HTML document for a route including serialized state.
383
395
  * Uses renderToDocument pattern with embedded route state.
396
+ *
397
+ * Security: meta/link attribute names are validated against
398
+ * `SAFE_ATTR_NAME`, URL attributes are routed through `sanitizeUrl`,
399
+ * `title` is HTML-escaped, and the embedded state script escapes
400
+ * `U+2028` / `U+2029` plus the usual `<`/`>`/`&` trio.
384
401
  */
385
402
  declare function renderRouteToDocument(url: string, routes: SSRRouteDef[], options?: {
386
403
  title?: string;
@@ -388,12 +405,17 @@ declare function renderRouteToDocument(url: string, routes: SSRRouteDef[], optio
388
405
  links?: Record<string, string>[];
389
406
  scripts?: string[];
390
407
  headExtra?: TrustedHTML;
408
+ nonce?: string;
391
409
  }): string;
392
410
  /**
393
411
  * Serialize route state for embedding in HTML.
394
412
  * Uses a specific key (__SIBU_ROUTE_STATE__) distinct from the generic SSR data key.
413
+ *
414
+ * Security: escapes `<`/`>`/`&` and the ES line-terminator pairs
415
+ * `U+2028` / `U+2029`. Supports an optional `nonce` attribute for
416
+ * strict-CSP compatibility.
395
417
  */
396
- declare function serializeRouteState(state: SSRRouteState): string;
418
+ declare function serializeRouteState(state: SSRRouteState, nonce?: string): string;
397
419
  /**
398
420
  * Deserialize route state on the client from server-embedded data.
399
421
  * Reads from window.__SIBU_ROUTE_STATE__.
@@ -430,7 +452,8 @@ declare function createSSRRouter(routes: SSRRouteDef[]): {
430
452
  links?: Record<string, string>[];
431
453
  scripts?: string[];
432
454
  headExtra?: TrustedHTML;
455
+ nonce?: string;
433
456
  }) => string;
434
457
  };
435
458
 
436
- export { type AsyncComponent, type AsyncRoute, type Component, type ComponentRoute, type Guard, type GuardResult, KeepAliveRoute, type LazyComponent, type NavigationFailure, type NavigationGuard, type NavigationNext, type NavigationResult, type NavigationTarget, Outlet, type Params, type RedirectRoute, Route, type RouteBase, type RouteContext, type RouteDef, type RouteMeta, type RouteTransitionOptions, RouterLink, type RouterOptions, type RouterPlugin, type SSRRouteDef, type SSRRouteState, type ScrollBehavior, type ScrollPosition, SibuRouter, Suspense, Trans, addRoute, afterEach, back, beforeEach, beforeResolve, buildURL, createMemoryRouter, createRouter, createSSRRouter, deserializeRouteState, destroyRouter, forward, getAvailableLocales, getLocale, getRouteInfo, getRouteTransition, go, hasRoute, hasTranslation, hydrateRouter, lazy, navigate, preloadRoute, push, registerTranslations, removeRoute, renderRouteToDocument, renderRouteToString, replace, resolveServerRoute, route, router, routerPlugin, routerState, serializeRouteState, setLocale, setRouteTransition, setRoutes, t };
459
+ export { type AsyncComponent, type AsyncRoute, type Component, type ComponentRoute, type Guard, type GuardResult, KeepAliveRoute, type LazyComponent, type LazyRoute, type NavigationFailure, type NavigationGuard, type NavigationNext, type NavigationResult, type NavigationTarget, Outlet, type Params, type RedirectRoute, Route, type RouteBase, type RouteContext, type RouteDef, type RouteMeta, type RouteTransitionOptions, RouterLink, type RouterOptions, type RouterPlugin, type SSRRouteDef, type SSRRouteState, type ScrollBehavior, type ScrollPosition, SibuRouter, Suspense, Trans, addRoute, afterEach, back, beforeEach, beforeResolve, buildURL, createMemoryRouter, createRouter, createSSRRouter, deserializeRouteState, destroyRouter, forward, getAvailableLocales, getLocale, getRouteInfo, getRouteTransition, go, hasRoute, hasTranslation, hydrateRouter, lazy, navigate, preloadRoute, push, registerTranslations, removeRoute, renderRouteToDocument, renderRouteToString, replace, resolveServerRoute, route, router, routerPlugin, routerState, serializeRouteState, setLocale, setRouteTransition, setRoutes, t };
package/dist/plugins.js CHANGED
@@ -18,10 +18,11 @@ import {
18
18
  preloadCritical,
19
19
  prerenderRoutes,
20
20
  satisfies
21
- } from "./chunk-RQGQSLQK.js";
21
+ } from "./chunk-M4NLBH4I.js";
22
22
  import {
23
+ escapeScriptJson,
23
24
  renderToString
24
- } from "./chunk-WUHJISPP.js";
25
+ } from "./chunk-3X2YG6YM.js";
25
26
  import {
26
27
  createPlugin,
27
28
  inject,
@@ -33,22 +34,26 @@ import {
33
34
  } from "./chunk-K5ZUMYVS.js";
34
35
  import {
35
36
  span
36
- } from "./chunk-GCOK2LC3.js";
37
+ } from "./chunk-32DY64NT.js";
38
+ import "./chunk-F3FA4F32.js";
37
39
  import {
38
40
  dispose,
39
41
  registerDisposer
40
- } from "./chunk-B7SWRFUT.js";
41
- import "./chunk-23VV7YD3.js";
42
+ } from "./chunk-PTQJDMRT.js";
43
+ import {
44
+ sanitizeUrl
45
+ } from "./chunk-CMBFNA7L.js";
42
46
  import {
43
47
  effect
44
- } from "./chunk-6SA3QQES.js";
45
- import "./chunk-CHJ27IGK.js";
48
+ } from "./chunk-CHF5OHIA.js";
49
+ import "./chunk-EUZND3CB.js";
50
+ import {
51
+ signal
52
+ } from "./chunk-WZSPOOER.js";
46
53
  import {
47
- signal,
48
54
  track
49
- } from "./chunk-V2XTI523.js";
50
- import "./chunk-UNXCEF6S.js";
51
- import "./chunk-MLKGABMK.js";
55
+ } from "./chunk-ZD6OAMTH.js";
56
+ import "./chunk-5X6PP2UK.js";
52
57
 
53
58
  // src/plugins/i18n.ts
54
59
  var [currentLocale, setLocaleInternal] = signal("en");
@@ -81,6 +86,10 @@ function getAvailableLocales() {
81
86
  }
82
87
 
83
88
  // src/plugins/router.ts
89
+ function isSafeNavigationTarget(path) {
90
+ if (path === "") return true;
91
+ return sanitizeUrl(path) !== "";
92
+ }
84
93
  var LRUCache = class {
85
94
  constructor(maxSize = 100) {
86
95
  this.cache = /* @__PURE__ */ new Map();
@@ -566,6 +575,11 @@ var _SibuRouter = class _SibuRouter {
566
575
  try {
567
576
  await this.navigator.navigate(async (signal2) => {
568
577
  const targetPath = this.resolvePath(to);
578
+ if (!isSafeNavigationTarget(targetPath)) {
579
+ const from2 = this.currentRouteGetter();
580
+ const toContext2 = this.createRouteContext(targetPath);
581
+ throw new NavigationFailureError("aborted", from2, toContext2);
582
+ }
569
583
  const from = this.currentRouteGetter();
570
584
  const toContext = this.createRouteContext(targetPath);
571
585
  if (this.isSameRoute(from, toContext)) {
@@ -595,6 +609,9 @@ var _SibuRouter = class _SibuRouter {
595
609
  const beforeEachResult = await this.guards.runBeforeEach(to, from, signal2);
596
610
  if (beforeEachResult !== true) {
597
611
  if (typeof beforeEachResult === "string") {
612
+ if (!isSafeNavigationTarget(beforeEachResult)) {
613
+ throw new NavigationFailureError("aborted", from, to);
614
+ }
598
615
  return this.performNavigation(this.createRouteContext(beforeEachResult), from, options, signal2, depth + 1);
599
616
  }
600
617
  throw new NavigationFailureError("aborted", from, to);
@@ -610,6 +627,9 @@ var _SibuRouter = class _SibuRouter {
610
627
  const result = await guard(to, from);
611
628
  if (result !== true) {
612
629
  if (typeof result === "string") {
630
+ if (!isSafeNavigationTarget(result)) {
631
+ throw new NavigationFailureError("aborted", from, to);
632
+ }
613
633
  return this.performNavigation(this.createRouteContext(result), from, options, signal2, depth + 1);
614
634
  }
615
635
  throw new NavigationFailureError("aborted", from, to);
@@ -624,12 +644,18 @@ var _SibuRouter = class _SibuRouter {
624
644
  `[SibuJS Router] Redirect to absolute URL "${redirectPath}" detected. Use relative paths for safer redirects.`
625
645
  );
626
646
  }
647
+ if (typeof redirectPath === "string" && !isSafeNavigationTarget(redirectPath)) {
648
+ throw new NavigationFailureError("aborted", from, to);
649
+ }
627
650
  return this.performNavigation(this.createRouteContext(redirectPath), from, options, signal2, depth + 1);
628
651
  }
629
652
  }
630
653
  const beforeResolveResult = await this.guards.runBeforeResolve(to, from, signal2);
631
654
  if (beforeResolveResult !== true) {
632
655
  if (typeof beforeResolveResult === "string") {
656
+ if (!isSafeNavigationTarget(beforeResolveResult)) {
657
+ throw new NavigationFailureError("aborted", from, to);
658
+ }
633
659
  return this.performNavigation(this.createRouteContext(beforeResolveResult), from, options, signal2, depth + 1);
634
660
  }
635
661
  throw new NavigationFailureError("aborted", from, to);
@@ -789,13 +815,31 @@ var NavigationFailureError = class extends Error {
789
815
  }
790
816
  };
791
817
  var globalRouter = null;
818
+ function normalizeRoutes(routes) {
819
+ return routes.map((route2) => {
820
+ const normalizedChildren = route2.children && route2.children.length > 0 ? normalizeRoutes(route2.children) : route2.children;
821
+ if ("lazy" in route2 && typeof route2.lazy === "function") {
822
+ const { lazy: importFn, ...rest } = route2;
823
+ const asyncRoute = {
824
+ ...rest,
825
+ component: lazy(importFn),
826
+ children: normalizedChildren
827
+ };
828
+ return asyncRoute;
829
+ }
830
+ if (normalizedChildren !== route2.children) {
831
+ return { ...route2, children: normalizedChildren };
832
+ }
833
+ return route2;
834
+ });
835
+ }
792
836
  function createRouter(routesOrOptions, options = {}) {
793
837
  if (globalRouter) {
794
838
  globalRouter.destroy();
795
839
  }
796
840
  let routes;
797
841
  if (Array.isArray(routesOrOptions)) {
798
- routes = routesOrOptions;
842
+ routes = normalizeRoutes(routesOrOptions);
799
843
  } else {
800
844
  options = routesOrOptions;
801
845
  routes = [];
@@ -805,7 +849,7 @@ function createRouter(routesOrOptions, options = {}) {
805
849
  }
806
850
  function setRoutes(routes) {
807
851
  if (!globalRouter) throw new Error("Router not initialized. Call createRouter() first.");
808
- globalRouter.updateRoutes(routes);
852
+ globalRouter.updateRoutes(normalizeRoutes(routes));
809
853
  }
810
854
  function route() {
811
855
  if (!globalRouter) throw new Error("Router not initialized. Call createRouter() first.");
@@ -1385,6 +1429,20 @@ function createMemoryRouter(routes, _initialPath = "/") {
1385
1429
  }
1386
1430
 
1387
1431
  // src/plugins/routerSSR.ts
1432
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
1433
+ function isForbiddenKey(key) {
1434
+ return FORBIDDEN_KEYS.has(key);
1435
+ }
1436
+ function safeDecode(raw) {
1437
+ try {
1438
+ return decodeURIComponent(raw);
1439
+ } catch {
1440
+ return raw;
1441
+ }
1442
+ }
1443
+ function nullObject() {
1444
+ return /* @__PURE__ */ Object.create(null);
1445
+ }
1388
1446
  function parseURL(url) {
1389
1447
  let remaining = url;
1390
1448
  let hash = "";
@@ -1400,19 +1458,23 @@ function parseURL(url) {
1400
1458
  remaining = remaining.slice(0, queryIndex);
1401
1459
  }
1402
1460
  const path = remaining || "/";
1403
- const query = {};
1461
+ const query = nullObject();
1404
1462
  if (queryString) {
1405
1463
  const pairs = queryString.split("&");
1406
1464
  for (const pair of pairs) {
1407
1465
  if (!pair) continue;
1408
1466
  const eqIndex = pair.indexOf("=");
1467
+ let key;
1468
+ let value;
1409
1469
  if (eqIndex === -1) {
1410
- query[decodeURIComponent(pair)] = "";
1470
+ key = safeDecode(pair);
1471
+ value = "";
1411
1472
  } else {
1412
- const key = decodeURIComponent(pair.slice(0, eqIndex));
1413
- const value = decodeURIComponent(pair.slice(eqIndex + 1));
1414
- query[key] = value;
1473
+ key = safeDecode(pair.slice(0, eqIndex));
1474
+ value = safeDecode(pair.slice(eqIndex + 1));
1415
1475
  }
1476
+ if (isForbiddenKey(key)) continue;
1477
+ query[key] = value;
1416
1478
  }
1417
1479
  }
1418
1480
  return { path, query, hash };
@@ -1470,10 +1532,12 @@ function matchRoute(path, routes, parentPath = "", parentChain = []) {
1470
1532
  const compiled = compilePattern(fullPath);
1471
1533
  const match = path.match(compiled.regex);
1472
1534
  if (match) {
1473
- const params = {};
1535
+ const params = nullObject();
1474
1536
  for (let i = 0; i < compiled.keys.length; i++) {
1537
+ const key = compiled.keys[i];
1538
+ if (isForbiddenKey(key)) continue;
1475
1539
  if (match[i + 1] !== void 0) {
1476
- params[compiled.keys[i]] = decodeURIComponent(match[i + 1]);
1540
+ params[key] = safeDecode(match[i + 1]);
1477
1541
  }
1478
1542
  }
1479
1543
  return {
@@ -1508,7 +1572,7 @@ function resolveServerRouteInternal(url, routes, depth) {
1508
1572
  return {
1509
1573
  route: {
1510
1574
  path: normalizedPath,
1511
- params: {},
1575
+ params: nullObject(),
1512
1576
  query,
1513
1577
  hash,
1514
1578
  meta: {}
@@ -1568,20 +1632,26 @@ function renderRouteToString(url, routes, _options) {
1568
1632
  function renderRouteToDocument(url, routes, options) {
1569
1633
  const { html, state } = renderRouteToString(url, routes, options);
1570
1634
  const opts = options || {};
1571
- const metaTags = (opts.meta || []).map(
1572
- (attrs) => `<meta ${Object.entries(attrs).map(([k, v]) => `${k}="${escapeAttr(v)}"`).join(" ")} />`
1573
- ).join("\n ");
1574
- const linkTags = (opts.links || []).map(
1575
- (attrs) => `<link ${Object.entries(attrs).map(([k, v]) => `${k}="${escapeAttr(v)}"`).join(" ")} />`
1576
- ).join("\n ");
1577
- const scriptTags = (opts.scripts || []).map((src) => `<script src="${escapeAttr(src)}"></script>`).join("\n ");
1578
- const stateScript = serializeRouteState(state);
1635
+ const metaTags = (opts.meta || []).map((attrs) => {
1636
+ const pairs = buildSafeAttrString(attrs);
1637
+ return pairs ? `<meta ${pairs} />` : "";
1638
+ }).filter(Boolean).join("\n ");
1639
+ const linkTags = (opts.links || []).map((attrs) => {
1640
+ const pairs = buildSafeAttrString(attrs);
1641
+ return pairs ? `<link ${pairs} />` : "";
1642
+ }).filter(Boolean).join("\n ");
1643
+ const scriptTags = (opts.scripts || []).map((src) => {
1644
+ const safe = sanitizeUrlLocal(String(src));
1645
+ if (!safe) return "";
1646
+ return `<script src="${escapeAttrLocal(safe)}"></script>`;
1647
+ }).filter(Boolean).join("\n ");
1648
+ const stateScript = serializeRouteState(state, opts.nonce);
1579
1649
  return `<!DOCTYPE html>
1580
1650
  <html>
1581
1651
  <head>
1582
1652
  <meta charset="UTF-8" />
1583
1653
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1584
- ${opts.title ? `<title>${escapeHtml(opts.title)}</title>` : ""}
1654
+ ${opts.title ? `<title>${escapeHtmlLocal(opts.title)}</title>` : ""}
1585
1655
  ${metaTags}
1586
1656
  ${linkTags}
1587
1657
  ${opts.headExtra || ""}
@@ -1593,9 +1663,10 @@ function renderRouteToDocument(url, routes, options) {
1593
1663
  </body>
1594
1664
  </html>`;
1595
1665
  }
1596
- function serializeRouteState(state) {
1597
- const json = JSON.stringify(state).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
1598
- return `<script>window.${SSR_ROUTE_STATE_KEY}=${json}</script>`;
1666
+ function serializeRouteState(state, nonce) {
1667
+ const json = escapeScriptJson(JSON.stringify(state));
1668
+ const nonceAttr = nonce ? ` nonce="${escapeAttrLocal(nonce)}"` : "";
1669
+ return `<script${nonceAttr}>window.${SSR_ROUTE_STATE_KEY}=${json}</script>`;
1599
1670
  }
1600
1671
  function deserializeRouteState() {
1601
1672
  if (typeof window === "undefined") return void 0;
@@ -1612,7 +1683,7 @@ function hydrateRouter(routes, options) {
1612
1683
  if (container && serverState.path) {
1613
1684
  const resolved = resolveServerRoute(serverState.path, routes);
1614
1685
  if (resolved.component) {
1615
- import("./ssr-3RXHP5ES.js").then(({ hydrate }) => {
1686
+ import("./ssr-4PBXAOO3.js").then(({ hydrate }) => {
1616
1687
  if (resolved.component) {
1617
1688
  hydrate(resolved.component, container);
1618
1689
  }
@@ -1633,11 +1704,59 @@ function createSSRRouter(routes) {
1633
1704
  }
1634
1705
  };
1635
1706
  }
1636
- function escapeHtml(str) {
1707
+ var SAFE_ATTR_NAME = /^[A-Za-z_:][-A-Za-z0-9_.:]*$/;
1708
+ function isSafeAttrName(name) {
1709
+ return SAFE_ATTR_NAME.test(name);
1710
+ }
1711
+ function isEventHandlerAttr(name) {
1712
+ if (name.length < 3) return false;
1713
+ const lower = name.toLowerCase();
1714
+ return lower[0] === "o" && lower[1] === "n" && lower.charCodeAt(2) >= 97 && lower.charCodeAt(2) <= 122;
1715
+ }
1716
+ var URL_ATTRS = /* @__PURE__ */ new Set([
1717
+ "href",
1718
+ "src",
1719
+ "action",
1720
+ "formaction",
1721
+ "cite",
1722
+ "poster",
1723
+ "background",
1724
+ "srcset",
1725
+ "ping",
1726
+ "manifest",
1727
+ "data",
1728
+ "xlink:href"
1729
+ ]);
1730
+ function sanitizeUrlLocal(url) {
1731
+ const trimmed = url.replace(/[\x00-\x20\x7f-\x9f]+/g, "").trim();
1732
+ if (!trimmed) return "";
1733
+ const lower = trimmed.toLowerCase();
1734
+ if (lower.startsWith("javascript:") || lower.startsWith("data:") || lower.startsWith("vbscript:") || lower.startsWith("blob:")) {
1735
+ return "";
1736
+ }
1737
+ return trimmed;
1738
+ }
1739
+ function buildSafeAttrString(attrs) {
1740
+ const out = [];
1741
+ for (const rawKey of Object.keys(attrs)) {
1742
+ if (!Object.hasOwn(attrs, rawKey)) continue;
1743
+ if (!isSafeAttrName(rawKey)) continue;
1744
+ if (isEventHandlerAttr(rawKey)) continue;
1745
+ const lowerKey = rawKey.toLowerCase();
1746
+ let value = String(attrs[rawKey]);
1747
+ if (URL_ATTRS.has(lowerKey)) {
1748
+ value = sanitizeUrlLocal(value);
1749
+ if (!value) continue;
1750
+ }
1751
+ out.push(`${rawKey}="${escapeAttrLocal(value)}"`);
1752
+ }
1753
+ return out.join(" ");
1754
+ }
1755
+ function escapeHtmlLocal(str) {
1637
1756
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1638
1757
  }
1639
- function escapeAttr(str) {
1640
- return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1758
+ function escapeAttrLocal(str) {
1759
+ return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1641
1760
  }
1642
1761
  export {
1643
1762
  KeepAliveRoute,
@@ -0,0 +1,40 @@
1
+ import {
2
+ collectStream,
3
+ deserializeState,
4
+ escapeScriptJson,
5
+ hydrate,
6
+ hydrateIslands,
7
+ hydrateProgressively,
8
+ island,
9
+ renderToDocument,
10
+ renderToReadableStream,
11
+ renderToStream,
12
+ renderToString,
13
+ renderToSuspenseStream,
14
+ resetSSRState,
15
+ serializeState,
16
+ ssrSuspense,
17
+ suspenseSwapScript,
18
+ trustHTML
19
+ } from "./chunk-3X2YG6YM.js";
20
+ import "./chunk-CMBFNA7L.js";
21
+ import "./chunk-5X6PP2UK.js";
22
+ export {
23
+ collectStream,
24
+ deserializeState,
25
+ escapeScriptJson,
26
+ hydrate,
27
+ hydrateIslands,
28
+ hydrateProgressively,
29
+ island,
30
+ renderToDocument,
31
+ renderToReadableStream,
32
+ renderToStream,
33
+ renderToString,
34
+ renderToSuspenseStream,
35
+ resetSSRState,
36
+ serializeState,
37
+ ssrSuspense,
38
+ suspenseSwapScript,
39
+ trustHTML
40
+ };