@reckona/mreact-router 0.0.97 → 0.0.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +4 -0
  2. package/dist/actions.d.ts +7 -0
  3. package/dist/actions.d.ts.map +1 -1
  4. package/dist/actions.js +26 -2
  5. package/dist/actions.js.map +1 -1
  6. package/dist/adapters/aws-lambda.d.ts.map +1 -1
  7. package/dist/adapters/aws-lambda.js +10 -7
  8. package/dist/adapters/aws-lambda.js.map +1 -1
  9. package/dist/build.d.ts +38 -1
  10. package/dist/build.d.ts.map +1 -1
  11. package/dist/build.js +1087 -304
  12. package/dist/build.js.map +1 -1
  13. package/dist/bundle-pipeline.d.ts +7 -0
  14. package/dist/bundle-pipeline.d.ts.map +1 -1
  15. package/dist/bundle-pipeline.js +121 -87
  16. package/dist/bundle-pipeline.js.map +1 -1
  17. package/dist/cache.d.ts.map +1 -1
  18. package/dist/cache.js +10 -8
  19. package/dist/cache.js.map +1 -1
  20. package/dist/cli-options.d.ts +1 -0
  21. package/dist/cli-options.d.ts.map +1 -1
  22. package/dist/cli-options.js +16 -0
  23. package/dist/cli-options.js.map +1 -1
  24. package/dist/cli.js +1 -0
  25. package/dist/cli.js.map +1 -1
  26. package/dist/client.d.ts +6 -1
  27. package/dist/client.d.ts.map +1 -1
  28. package/dist/client.js +113 -8
  29. package/dist/client.js.map +1 -1
  30. package/dist/csrf.d.ts.map +1 -1
  31. package/dist/csrf.js +7 -3
  32. package/dist/csrf.js.map +1 -1
  33. package/dist/http.d.ts.map +1 -1
  34. package/dist/http.js +18 -1
  35. package/dist/http.js.map +1 -1
  36. package/dist/import-policy.d.ts.map +1 -1
  37. package/dist/import-policy.js +7 -3
  38. package/dist/import-policy.js.map +1 -1
  39. package/dist/index.d.ts +4 -3
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/link.d.ts +5 -2
  43. package/dist/link.d.ts.map +1 -1
  44. package/dist/link.js +92 -3
  45. package/dist/link.js.map +1 -1
  46. package/dist/logger.d.ts +7 -1
  47. package/dist/logger.d.ts.map +1 -1
  48. package/dist/logger.js.map +1 -1
  49. package/dist/module-runner.d.ts +2 -0
  50. package/dist/module-runner.d.ts.map +1 -1
  51. package/dist/module-runner.js +1 -0
  52. package/dist/module-runner.js.map +1 -1
  53. package/dist/native-route-matcher.d.ts +2 -1
  54. package/dist/native-route-matcher.d.ts.map +1 -1
  55. package/dist/native-route-matcher.js +13 -2
  56. package/dist/native-route-matcher.js.map +1 -1
  57. package/dist/render.d.ts.map +1 -1
  58. package/dist/render.js +117 -2
  59. package/dist/render.js.map +1 -1
  60. package/dist/route-source.d.ts +4 -4
  61. package/dist/route-source.d.ts.map +1 -1
  62. package/dist/route-source.js +8 -8
  63. package/dist/route-source.js.map +1 -1
  64. package/dist/route-styles.d.ts +6 -0
  65. package/dist/route-styles.d.ts.map +1 -1
  66. package/dist/route-styles.js +10 -1
  67. package/dist/route-styles.js.map +1 -1
  68. package/dist/serve.d.ts +5 -0
  69. package/dist/serve.d.ts.map +1 -1
  70. package/dist/serve.js +7 -4
  71. package/dist/serve.js.map +1 -1
  72. package/package.json +11 -11
  73. package/src/actions.ts +42 -2
  74. package/src/adapters/aws-lambda.ts +33 -16
  75. package/src/build.ts +1578 -397
  76. package/src/bundle-pipeline.ts +136 -88
  77. package/src/cache.ts +13 -8
  78. package/src/cli-options.ts +20 -0
  79. package/src/cli.ts +1 -0
  80. package/src/client.ts +145 -14
  81. package/src/csrf.ts +8 -3
  82. package/src/http.ts +21 -1
  83. package/src/import-policy.ts +8 -3
  84. package/src/index.ts +5 -0
  85. package/src/link.ts +129 -8
  86. package/src/logger.ts +9 -1
  87. package/src/module-runner.ts +4 -0
  88. package/src/native-route-matcher.ts +13 -2
  89. package/src/render.ts +177 -2
  90. package/src/route-source.ts +8 -8
  91. package/src/route-styles.ts +17 -1
  92. package/src/serve.ts +11 -6
package/dist/render.js CHANGED
@@ -683,6 +683,13 @@ async function renderAppRequestInternal(options) {
683
683
  vitePlugins: options.vitePlugins,
684
684
  });
685
685
  html = injectHeadMetadata(html, metadata);
686
+ warnIfCspNonceWouldBlockInlineTags({
687
+ html,
688
+ logger: options.logger,
689
+ metadata,
690
+ request: options.request,
691
+ serverModuleCacheVersion: options.serverModuleCacheVersion,
692
+ });
686
693
  html = injectAuthSessionClaims(html, originalAnalysis.authIncludesClaims ? currentAuthClaims() : undefined);
687
694
  html = injectQueryState(html, dehydrate(queryClient));
688
695
  const response = withOptionalActionCookie(htmlResponse(`<!DOCTYPE html>${clientNavigationHeadTags({
@@ -897,6 +904,13 @@ async function renderAppRequestInternal(options) {
897
904
  finishRenderTimingPhase(timing, phaseStartedAt, "metadataMs");
898
905
  phaseStartedAt = renderTimingPhaseStartedAt(timing);
899
906
  html = injectHeadMetadata(html, metadata);
907
+ warnIfCspNonceWouldBlockInlineTags({
908
+ html,
909
+ logger: options.logger,
910
+ metadata,
911
+ request: options.request,
912
+ serverModuleCacheVersion: options.serverModuleCacheVersion,
913
+ });
900
914
  html = injectAuthSessionClaims(html, originalAnalysis.authIncludesClaims ? currentAuthClaims() : undefined);
901
915
  html = injectQueryState(html, dehydrate(queryClient));
902
916
  const response = withOptionalActionCookie(htmlResponse(`<!DOCTYPE html>${clientNavigationHeadTags({
@@ -1513,6 +1527,7 @@ export async function bundleMiddlewareModuleCode(options) {
1513
1527
  code: options.code,
1514
1528
  filename: options.file,
1515
1529
  platform: "node",
1530
+ root: options.importPolicy?.projectRoot,
1516
1531
  plugins: [
1517
1532
  fileImportMetaUrlPlugin(),
1518
1533
  createAppRouterImportPolicyPlugin({
@@ -2633,13 +2648,113 @@ function hasGenerateMetadataExport(code) {
2633
2648
  function usesRuntimeCacheControl(code) {
2634
2649
  return /\bcacheControl\s*\(/.test(code);
2635
2650
  }
2651
+ function warnIfCspNonceWouldBlockInlineTags(options) {
2652
+ if (options.serverModuleCacheVersion !== undefined ||
2653
+ process.env.NODE_ENV === "production" ||
2654
+ options.metadata?.csp?.disable === true ||
2655
+ options.metadata?.csp?.directives === undefined ||
2656
+ !/<(?:script|style)\b/i.test(options.html)) {
2657
+ return;
2658
+ }
2659
+ const scriptNonces = cspDirectiveNonces(options.metadata.csp, "script-src");
2660
+ const styleNonces = cspDirectiveNonces(options.metadata.csp, "style-src");
2661
+ if (scriptNonces.size === 0 && styleNonces.size === 0) {
2662
+ return;
2663
+ }
2664
+ for (const tag of inlineCspTags(options.html)) {
2665
+ if (tag.name === "script") {
2666
+ if (scriptNonces.size > 0 &&
2667
+ !scriptHasExternalSourceOrInertType(tag.attributes) &&
2668
+ tag.content.trim() !== "" &&
2669
+ !tagHasMatchingNonce(tag.attributes, scriptNonces)) {
2670
+ warnCspInlineNonceMismatch(options, "script-src", "script");
2671
+ }
2672
+ continue;
2673
+ }
2674
+ if (styleNonces.size > 0 &&
2675
+ tag.content.trim() !== "" &&
2676
+ !tagHasMatchingNonce(tag.attributes, styleNonces)) {
2677
+ warnCspInlineNonceMismatch(options, "style-src", "style");
2678
+ }
2679
+ }
2680
+ }
2681
+ function warnCspInlineNonceMismatch(options, directive, tag) {
2682
+ const message = tag === "script"
2683
+ ? "mreact router: CSP script-src uses a nonce, but an inline <script> without a matching nonce will be blocked. Add the script through metadata.head with nonce: true, move it to an external script, or remove script-src for this route."
2684
+ : "mreact router: CSP style-src uses a nonce, but an inline <style> without a matching nonce will be blocked. Add the style through metadata.head with nonce: true, move it to an external stylesheet, or remove style-src for this route.";
2685
+ if (options.logger === undefined) {
2686
+ console.warn(message);
2687
+ return;
2688
+ }
2689
+ emitRouterLog(options.logger, "warn", {
2690
+ directive,
2691
+ path: new URL(options.request.url).pathname,
2692
+ tag,
2693
+ type: "router:csp:inline-nonce-warning",
2694
+ });
2695
+ }
2696
+ function cspDirectiveNonces(csp, directive) {
2697
+ const values = csp.directives?.[directive];
2698
+ const nonces = new Set();
2699
+ if (values !== undefined && csp.nonce !== undefined) {
2700
+ nonces.add(csp.nonce);
2701
+ }
2702
+ for (const value of Array.isArray(values) ? values : values === undefined ? [] : [values]) {
2703
+ const match = /^'nonce-([^']+)'$/.exec(value);
2704
+ if (match?.[1] !== undefined) {
2705
+ nonces.add(match[1]);
2706
+ }
2707
+ }
2708
+ return nonces;
2709
+ }
2710
+ function inlineCspTags(html) {
2711
+ const tags = [];
2712
+ const pattern = /<(script|style)\b([^>]*)>([\s\S]*?)<\/\1>/gi;
2713
+ let match;
2714
+ while ((match = pattern.exec(html)) !== null) {
2715
+ const name = match[1]?.toLowerCase();
2716
+ if (name !== "script" && name !== "style") {
2717
+ continue;
2718
+ }
2719
+ tags.push({
2720
+ attributes: parseTagAttributes(match[2] ?? ""),
2721
+ content: match[3] ?? "",
2722
+ name,
2723
+ });
2724
+ }
2725
+ return tags;
2726
+ }
2727
+ function parseTagAttributes(source) {
2728
+ const attributes = new Map();
2729
+ const pattern = /([^\s=/>]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
2730
+ let match;
2731
+ while ((match = pattern.exec(source)) !== null) {
2732
+ const name = match[1]?.toLowerCase();
2733
+ if (name === undefined) {
2734
+ continue;
2735
+ }
2736
+ attributes.set(name, match[2] ?? match[3] ?? match[4] ?? "");
2737
+ }
2738
+ return attributes;
2739
+ }
2740
+ function scriptHasExternalSourceOrInertType(attributes) {
2741
+ if (attributes.has("src")) {
2742
+ return true;
2743
+ }
2744
+ const type = attributes.get("type")?.trim().toLowerCase();
2745
+ return type === "application/json" || type === "application/ld+json";
2746
+ }
2747
+ function tagHasMatchingNonce(attributes, expectedNonces) {
2748
+ const nonce = attributes.get("nonce");
2749
+ return nonce !== undefined && expectedNonces.has(nonce);
2750
+ }
2636
2751
  function injectQueryState(html, state) {
2637
2752
  if (state.queries.length === 0) {
2638
2753
  return html;
2639
2754
  }
2640
2755
  const script = `<script type="application/json" id="${__MREACT_QUERY_STATE_SCRIPT_ID}">${escapeJsonForHtml(JSON.stringify(state))}</script>`;
2641
2756
  return /<\/body>/i.test(html)
2642
- ? html.replace(/<\/body>/i, `${script}</body>`)
2757
+ ? html.replace(/<\/body>/i, () => `${script}</body>`)
2643
2758
  : `${html}${script}`;
2644
2759
  }
2645
2760
  function injectAuthSessionClaims(html, claims) {
@@ -2648,7 +2763,7 @@ function injectAuthSessionClaims(html, claims) {
2648
2763
  }
2649
2764
  const script = `<script type="application/json" id="${authSessionScriptId}">${escapeJsonForHtml(JSON.stringify(claims))}</script>`;
2650
2765
  return /<\/body>/i.test(html)
2651
- ? html.replace(/<\/body>/i, `${script}</body>`)
2766
+ ? html.replace(/<\/body>/i, () => `${script}</body>`)
2652
2767
  : `${html}${script}`;
2653
2768
  }
2654
2769
  function authIncludesClaims(code) {