@frontmcp/uipack 1.8.0 → 1.8.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.
@@ -1 +1 @@
1
- {"version":3,"file":"content-detector.d.ts","sourceRoot":"","sources":["../../src/adapters/content-detector.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,SAAS,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAyBzF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,CAsCrE"}
1
+ {"version":3,"file":"content-detector.d.ts","sourceRoot":"","sources":["../../src/adapters/content-detector.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,SAAS,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AA2BzF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,CAsCrE"}
package/adapters/index.js CHANGED
@@ -514,8 +514,9 @@ function escapeHtml(str) {
514
514
  const s = String(str);
515
515
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
516
516
  }
517
- function escapeScriptClose(jsonString) {
518
- return jsonString.replace(/<\//g, "<\\/");
517
+ var SCRIPT_UNSAFE_CHARACTERS = /[<>&\u2028\u2029]/g;
518
+ function toUnicodeEscape(character) {
519
+ return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`;
519
520
  }
520
521
  function safeJsonForScript(value) {
521
522
  if (value === void 0) {
@@ -531,7 +532,7 @@ function safeJsonForScript(value) {
531
532
  if (jsonString === void 0) {
532
533
  return "null";
533
534
  }
534
- return escapeScriptClose(jsonString);
535
+ return jsonString.replace(SCRIPT_UNSAFE_CHARACTERS, toUnicodeEscape);
535
536
  } catch {
536
537
  return '{"error":"Value could not be serialized"}';
537
538
  }
@@ -1828,14 +1829,16 @@ var REQUIRED_PLACEHOLDERS = ["CONTENT"];
1828
1829
  var OPTIONAL_PLACEHOLDERS = ["CSP", "DATA", "BRIDGE", "TITLE"];
1829
1830
 
1830
1831
  // libs/uipack/src/shell/custom-shell-applier.ts
1832
+ var PLACEHOLDER_PATTERN = new RegExp(`\\{\\{(${SHELL_PLACEHOLDER_NAMES.join("|")})\\}\\}`, "g");
1833
+ var VALUE_KEYS = {
1834
+ CSP: "csp",
1835
+ DATA: "data",
1836
+ BRIDGE: "bridge",
1837
+ CONTENT: "content",
1838
+ TITLE: "title"
1839
+ };
1831
1840
  function applyShellTemplate(template, values) {
1832
- let result = template;
1833
- result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
1834
- result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
1835
- result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
1836
- result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
1837
- result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
1838
- return result;
1841
+ return template.replace(PLACEHOLDER_PATTERN, (_token, name) => values[VALUE_KEYS[name]]);
1839
1842
  }
1840
1843
 
1841
1844
  // libs/uipack/src/shell/custom-shell-validator.ts
@@ -1907,9 +1910,7 @@ function createTemplateHelpers() {
1907
1910
  uniqueId: (prefix = "mcp") => {
1908
1911
  return `${prefix}-${++_uniqueIdCounter}`;
1909
1912
  },
1910
- jsonEmbed: (data) => {
1911
- return escapeScriptClose(JSON.stringify(data));
1912
- }
1913
+ jsonEmbed: (data) => safeJsonForScript(data)
1913
1914
  };
1914
1915
  }
1915
1916
 
@@ -2585,6 +2586,7 @@ var MERMAID_PREFIXES = [
2585
2586
  "xychart"
2586
2587
  ];
2587
2588
  var PDF_MAGIC = "JVBERi";
2589
+ var BASE64_PATTERN = /^[A-Za-z0-9+/=\r\n]+$/;
2588
2590
  function detectContentType(value) {
2589
2591
  if (value === null || value === void 0) {
2590
2592
  return "text";
@@ -2599,7 +2601,7 @@ function detectContentType(value) {
2599
2601
  if (typeof value === "string") {
2600
2602
  const trimmed = value.trimStart();
2601
2603
  if (trimmed.startsWith(PDF_MAGIC)) {
2602
- return "pdf";
2604
+ return BASE64_PATTERN.test(trimmed.trimEnd()) ? "pdf" : "text";
2603
2605
  }
2604
2606
  for (const prefix of MERMAID_PREFIXES) {
2605
2607
  if (trimmed.startsWith(prefix)) {
@@ -2661,7 +2663,7 @@ function buildPdfHtml(base64) {
2661
2663
  import * as pdfjsLib from 'https://esm.sh/pdfjs-dist@4/build/pdf.min.mjs';
2662
2664
  pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://esm.sh/pdfjs-dist@4/build/pdf.worker.min.mjs';
2663
2665
 
2664
- const base64 = ${JSON.stringify(base64)};
2666
+ const base64 = ${safeJsonForScript(base64)};
2665
2667
  const binaryStr = atob(base64);
2666
2668
  const bytes = new Uint8Array(binaryStr.length);
2667
2669
  for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
@@ -2756,6 +2758,12 @@ function buildCspConfig(resolver) {
2756
2758
  }
2757
2759
  return { resourceDomains: cspResourceDomains, connectDomains: cspConnectDomains };
2758
2760
  }
2761
+ function renderUnwrappedResult(rawResult) {
2762
+ if (typeof rawResult !== "string") {
2763
+ return `<pre>${escapeHtml(JSON.stringify(rawResult, null, 2))}</pre>`;
2764
+ }
2765
+ return detectContentType(rawResult) === "html" ? rawResult : escapeHtml(rawResult);
2766
+ }
2759
2767
  function renderToolTemplate(options) {
2760
2768
  const { toolName, input, output, template, resolver, platformType, sizing } = options;
2761
2769
  const uiType = detectUIType(template);
@@ -2794,8 +2802,7 @@ function renderToolTemplate(options) {
2794
2802
  if (wrapped) {
2795
2803
  html = wrapped;
2796
2804
  } else {
2797
- const textContent = typeof rawResult === "string" ? rawResult : `<pre>${JSON.stringify(rawResult, null, 2)}</pre>`;
2798
- const shellResult = buildShell(textContent, shellConfig);
2805
+ const shellResult = buildShell(renderUnwrappedResult(rawResult), shellConfig);
2799
2806
  html = shellResult.html;
2800
2807
  hash = shellResult.hash;
2801
2808
  size = shellResult.size;
@@ -1 +1 @@
1
- {"version":3,"file":"template-renderer.d.ts","sourceRoot":"","sources":["../../src/adapters/template-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAKnD;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,kBAAkB;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,qDAAqD;IACrD,QAAQ,EAAE,OAAO,CAAC;IAClB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAChC;;;;OAIG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAsBD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,wBAAwB,CAyG/F"}
1
+ {"version":3,"file":"template-renderer.d.ts","sourceRoot":"","sources":["../../src/adapters/template-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAOnD;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,gBAAgB;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,kBAAkB;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,qDAAqD;IACrD,QAAQ,EAAE,OAAO,CAAC;IAClB,4CAA4C;IAC5C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAChC;;;;OAIG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAmCD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,yBAAyB,GAAG,wBAAwB,CAsG/F"}
@@ -450,8 +450,9 @@ function createEsmShResolver(options = {}) {
450
450
  }
451
451
 
452
452
  // libs/uipack/src/utils/index.ts
453
- function escapeScriptClose(jsonString) {
454
- return jsonString.replace(/<\//g, "<\\/");
453
+ var SCRIPT_UNSAFE_CHARACTERS = /[<>&\u2028\u2029]/g;
454
+ function toUnicodeEscape(character) {
455
+ return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`;
455
456
  }
456
457
  function safeJsonForScript(value) {
457
458
  if (value === void 0) {
@@ -467,7 +468,7 @@ function safeJsonForScript(value) {
467
468
  if (jsonString === void 0) {
468
469
  return "null";
469
470
  }
470
- return escapeScriptClose(jsonString);
471
+ return jsonString.replace(SCRIPT_UNSAFE_CHARACTERS, toUnicodeEscape);
471
472
  } catch {
472
473
  return '{"error":"Value could not be serialized"}';
473
474
  }
@@ -2070,14 +2071,16 @@ var REQUIRED_PLACEHOLDERS = ["CONTENT"];
2070
2071
  var OPTIONAL_PLACEHOLDERS = ["CSP", "DATA", "BRIDGE", "TITLE"];
2071
2072
 
2072
2073
  // libs/uipack/src/shell/custom-shell-applier.ts
2074
+ var PLACEHOLDER_PATTERN = new RegExp(`\\{\\{(${SHELL_PLACEHOLDER_NAMES.join("|")})\\}\\}`, "g");
2075
+ var VALUE_KEYS = {
2076
+ CSP: "csp",
2077
+ DATA: "data",
2078
+ BRIDGE: "bridge",
2079
+ CONTENT: "content",
2080
+ TITLE: "title"
2081
+ };
2073
2082
  function applyShellTemplate(template, values) {
2074
- let result = template;
2075
- result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
2076
- result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
2077
- result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
2078
- result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
2079
- result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
2080
- return result;
2083
+ return template.replace(PLACEHOLDER_PATTERN, (_token, name) => values[VALUE_KEYS[name]]);
2081
2084
  }
2082
2085
 
2083
2086
  // libs/uipack/src/shell/custom-shell-validator.ts
@@ -482,8 +482,9 @@ function escapeHtml(str) {
482
482
  const s = String(str);
483
483
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
484
484
  }
485
- function escapeScriptClose(jsonString) {
486
- return jsonString.replace(/<\//g, "<\\/");
485
+ var SCRIPT_UNSAFE_CHARACTERS = /[<>&\u2028\u2029]/g;
486
+ function toUnicodeEscape(character) {
487
+ return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`;
487
488
  }
488
489
  function safeJsonForScript(value) {
489
490
  if (value === void 0) {
@@ -499,7 +500,7 @@ function safeJsonForScript(value) {
499
500
  if (jsonString === void 0) {
500
501
  return "null";
501
502
  }
502
- return escapeScriptClose(jsonString);
503
+ return jsonString.replace(SCRIPT_UNSAFE_CHARACTERS, toUnicodeEscape);
503
504
  } catch {
504
505
  return '{"error":"Value could not be serialized"}';
505
506
  }
@@ -1796,14 +1797,16 @@ var REQUIRED_PLACEHOLDERS = ["CONTENT"];
1796
1797
  var OPTIONAL_PLACEHOLDERS = ["CSP", "DATA", "BRIDGE", "TITLE"];
1797
1798
 
1798
1799
  // libs/uipack/src/shell/custom-shell-applier.ts
1800
+ var PLACEHOLDER_PATTERN = new RegExp(`\\{\\{(${SHELL_PLACEHOLDER_NAMES.join("|")})\\}\\}`, "g");
1801
+ var VALUE_KEYS = {
1802
+ CSP: "csp",
1803
+ DATA: "data",
1804
+ BRIDGE: "bridge",
1805
+ CONTENT: "content",
1806
+ TITLE: "title"
1807
+ };
1799
1808
  function applyShellTemplate(template, values) {
1800
- let result = template;
1801
- result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
1802
- result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
1803
- result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
1804
- result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
1805
- result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
1806
- return result;
1809
+ return template.replace(PLACEHOLDER_PATTERN, (_token, name) => values[VALUE_KEYS[name]]);
1807
1810
  }
1808
1811
 
1809
1812
  // libs/uipack/src/shell/custom-shell-validator.ts
@@ -1875,9 +1878,7 @@ function createTemplateHelpers() {
1875
1878
  uniqueId: (prefix = "mcp") => {
1876
1879
  return `${prefix}-${++_uniqueIdCounter}`;
1877
1880
  },
1878
- jsonEmbed: (data) => {
1879
- return escapeScriptClose(JSON.stringify(data));
1880
- }
1881
+ jsonEmbed: (data) => safeJsonForScript(data)
1881
1882
  };
1882
1883
  }
1883
1884
 
@@ -2553,6 +2554,7 @@ var MERMAID_PREFIXES = [
2553
2554
  "xychart"
2554
2555
  ];
2555
2556
  var PDF_MAGIC = "JVBERi";
2557
+ var BASE64_PATTERN = /^[A-Za-z0-9+/=\r\n]+$/;
2556
2558
  function detectContentType(value) {
2557
2559
  if (value === null || value === void 0) {
2558
2560
  return "text";
@@ -2567,7 +2569,7 @@ function detectContentType(value) {
2567
2569
  if (typeof value === "string") {
2568
2570
  const trimmed = value.trimStart();
2569
2571
  if (trimmed.startsWith(PDF_MAGIC)) {
2570
- return "pdf";
2572
+ return BASE64_PATTERN.test(trimmed.trimEnd()) ? "pdf" : "text";
2571
2573
  }
2572
2574
  for (const prefix of MERMAID_PREFIXES) {
2573
2575
  if (trimmed.startsWith(prefix)) {
@@ -2629,7 +2631,7 @@ function buildPdfHtml(base64) {
2629
2631
  import * as pdfjsLib from 'https://esm.sh/pdfjs-dist@4/build/pdf.min.mjs';
2630
2632
  pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://esm.sh/pdfjs-dist@4/build/pdf.worker.min.mjs';
2631
2633
 
2632
- const base64 = ${JSON.stringify(base64)};
2634
+ const base64 = ${safeJsonForScript(base64)};
2633
2635
  const binaryStr = atob(base64);
2634
2636
  const bytes = new Uint8Array(binaryStr.length);
2635
2637
  for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
@@ -2724,6 +2726,12 @@ function buildCspConfig(resolver) {
2724
2726
  }
2725
2727
  return { resourceDomains: cspResourceDomains, connectDomains: cspConnectDomains };
2726
2728
  }
2729
+ function renderUnwrappedResult(rawResult) {
2730
+ if (typeof rawResult !== "string") {
2731
+ return `<pre>${escapeHtml(JSON.stringify(rawResult, null, 2))}</pre>`;
2732
+ }
2733
+ return detectContentType(rawResult) === "html" ? rawResult : escapeHtml(rawResult);
2734
+ }
2727
2735
  function renderToolTemplate(options) {
2728
2736
  const { toolName, input, output, template, resolver, platformType, sizing } = options;
2729
2737
  const uiType = detectUIType(template);
@@ -2762,8 +2770,7 @@ function renderToolTemplate(options) {
2762
2770
  if (wrapped) {
2763
2771
  html = wrapped;
2764
2772
  } else {
2765
- const textContent = typeof rawResult === "string" ? rawResult : `<pre>${JSON.stringify(rawResult, null, 2)}</pre>`;
2766
- const shellResult = buildShell(textContent, shellConfig);
2773
+ const shellResult = buildShell(renderUnwrappedResult(rawResult), shellConfig);
2767
2774
  html = shellResult.html;
2768
2775
  hash = shellResult.hash;
2769
2776
  size = shellResult.size;
@@ -421,8 +421,9 @@ function createEsmShResolver(options = {}) {
421
421
  }
422
422
 
423
423
  // libs/uipack/src/utils/index.ts
424
- function escapeScriptClose(jsonString) {
425
- return jsonString.replace(/<\//g, "<\\/");
424
+ var SCRIPT_UNSAFE_CHARACTERS = /[<>&\u2028\u2029]/g;
425
+ function toUnicodeEscape(character) {
426
+ return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`;
426
427
  }
427
428
  function safeJsonForScript(value) {
428
429
  if (value === void 0) {
@@ -438,7 +439,7 @@ function safeJsonForScript(value) {
438
439
  if (jsonString === void 0) {
439
440
  return "null";
440
441
  }
441
- return escapeScriptClose(jsonString);
442
+ return jsonString.replace(SCRIPT_UNSAFE_CHARACTERS, toUnicodeEscape);
442
443
  } catch {
443
444
  return '{"error":"Value could not be serialized"}';
444
445
  }
@@ -2041,14 +2042,16 @@ var REQUIRED_PLACEHOLDERS = ["CONTENT"];
2041
2042
  var OPTIONAL_PLACEHOLDERS = ["CSP", "DATA", "BRIDGE", "TITLE"];
2042
2043
 
2043
2044
  // libs/uipack/src/shell/custom-shell-applier.ts
2045
+ var PLACEHOLDER_PATTERN = new RegExp(`\\{\\{(${SHELL_PLACEHOLDER_NAMES.join("|")})\\}\\}`, "g");
2046
+ var VALUE_KEYS = {
2047
+ CSP: "csp",
2048
+ DATA: "data",
2049
+ BRIDGE: "bridge",
2050
+ CONTENT: "content",
2051
+ TITLE: "title"
2052
+ };
2044
2053
  function applyShellTemplate(template, values) {
2045
- let result = template;
2046
- result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
2047
- result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
2048
- result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
2049
- result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
2050
- result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
2051
- return result;
2054
+ return template.replace(PLACEHOLDER_PATTERN, (_token, name) => values[VALUE_KEYS[name]]);
2052
2055
  }
2053
2056
 
2054
2057
  // libs/uipack/src/shell/custom-shell-validator.ts
package/esm/index.mjs CHANGED
@@ -107,6 +107,10 @@ function escapeJsString(str) {
107
107
  function escapeScriptClose(jsonString) {
108
108
  return jsonString.replace(/<\//g, "<\\/");
109
109
  }
110
+ var SCRIPT_UNSAFE_CHARACTERS = /[<>&\u2028\u2029]/g;
111
+ function toUnicodeEscape(character) {
112
+ return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`;
113
+ }
110
114
  function safeJsonForScript(value) {
111
115
  if (value === void 0) {
112
116
  return "null";
@@ -121,7 +125,7 @@ function safeJsonForScript(value) {
121
125
  if (jsonString === void 0) {
122
126
  return "null";
123
127
  }
124
- return escapeScriptClose(jsonString);
128
+ return jsonString.replace(SCRIPT_UNSAFE_CHARACTERS, toUnicodeEscape);
125
129
  } catch {
126
130
  return '{"error":"Value could not be serialized"}';
127
131
  }
@@ -2885,14 +2889,16 @@ function isNpmShellSource(source) {
2885
2889
  }
2886
2890
 
2887
2891
  // libs/uipack/src/shell/custom-shell-applier.ts
2892
+ var PLACEHOLDER_PATTERN = new RegExp(`\\{\\{(${SHELL_PLACEHOLDER_NAMES.join("|")})\\}\\}`, "g");
2893
+ var VALUE_KEYS = {
2894
+ CSP: "csp",
2895
+ DATA: "data",
2896
+ BRIDGE: "bridge",
2897
+ CONTENT: "content",
2898
+ TITLE: "title"
2899
+ };
2888
2900
  function applyShellTemplate(template, values) {
2889
- let result = template;
2890
- result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
2891
- result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
2892
- result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
2893
- result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
2894
- result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
2895
- return result;
2901
+ return template.replace(PLACEHOLDER_PATTERN, (_token, name) => values[VALUE_KEYS[name]]);
2896
2902
  }
2897
2903
 
2898
2904
  // libs/uipack/src/shell/custom-shell-validator.ts
@@ -2964,9 +2970,7 @@ function createTemplateHelpers() {
2964
2970
  uniqueId: (prefix = "mcp") => {
2965
2971
  return `${prefix}-${++_uniqueIdCounter}`;
2966
2972
  },
2967
- jsonEmbed: (data) => {
2968
- return escapeScriptClose(JSON.stringify(data));
2969
- }
2973
+ jsonEmbed: (data) => safeJsonForScript(data)
2970
2974
  };
2971
2975
  }
2972
2976
 
@@ -3821,6 +3825,7 @@ var MERMAID_PREFIXES = [
3821
3825
  "xychart"
3822
3826
  ];
3823
3827
  var PDF_MAGIC = "JVBERi";
3828
+ var BASE64_PATTERN = /^[A-Za-z0-9+/=\r\n]+$/;
3824
3829
  function detectContentType(value) {
3825
3830
  if (value === null || value === void 0) {
3826
3831
  return "text";
@@ -3835,7 +3840,7 @@ function detectContentType(value) {
3835
3840
  if (typeof value === "string") {
3836
3841
  const trimmed = value.trimStart();
3837
3842
  if (trimmed.startsWith(PDF_MAGIC)) {
3838
- return "pdf";
3843
+ return BASE64_PATTERN.test(trimmed.trimEnd()) ? "pdf" : "text";
3839
3844
  }
3840
3845
  for (const prefix of MERMAID_PREFIXES) {
3841
3846
  if (trimmed.startsWith(prefix)) {
@@ -3897,7 +3902,7 @@ function buildPdfHtml(base64) {
3897
3902
  import * as pdfjsLib from 'https://esm.sh/pdfjs-dist@4/build/pdf.min.mjs';
3898
3903
  pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://esm.sh/pdfjs-dist@4/build/pdf.worker.min.mjs';
3899
3904
 
3900
- const base64 = ${JSON.stringify(base64)};
3905
+ const base64 = ${safeJsonForScript(base64)};
3901
3906
  const binaryStr = atob(base64);
3902
3907
  const bytes = new Uint8Array(binaryStr.length);
3903
3908
  for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
@@ -3992,6 +3997,12 @@ function buildCspConfig(resolver) {
3992
3997
  }
3993
3998
  return { resourceDomains: cspResourceDomains, connectDomains: cspConnectDomains };
3994
3999
  }
4000
+ function renderUnwrappedResult(rawResult) {
4001
+ if (typeof rawResult !== "string") {
4002
+ return `<pre>${escapeHtml(JSON.stringify(rawResult, null, 2))}</pre>`;
4003
+ }
4004
+ return detectContentType(rawResult) === "html" ? rawResult : escapeHtml(rawResult);
4005
+ }
3995
4006
  function renderToolTemplate(options) {
3996
4007
  const { toolName, input, output, template, resolver, platformType, sizing } = options;
3997
4008
  const uiType = detectUIType(template);
@@ -4030,8 +4041,7 @@ function renderToolTemplate(options) {
4030
4041
  if (wrapped) {
4031
4042
  html = wrapped;
4032
4043
  } else {
4033
- const textContent = typeof rawResult === "string" ? rawResult : `<pre>${JSON.stringify(rawResult, null, 2)}</pre>`;
4034
- const shellResult = buildShell(textContent, shellConfig);
4044
+ const shellResult = buildShell(renderUnwrappedResult(rawResult), shellConfig);
4035
4045
  html = shellResult.html;
4036
4046
  hash = shellResult.hash;
4037
4047
  size = shellResult.size;
package/esm/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/uipack",
3
- "version": "1.8.0",
3
+ "version": "1.8.1",
4
4
  "description": "FrontMCP UIpack - HTML shell builder, pluggable import resolver, and NPM component loader for MCP UI (React-free core)",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -61,6 +61,6 @@
61
61
  "typescript": "^5.9.3"
62
62
  },
63
63
  "peerDependencies": {
64
- "@frontmcp/lazy-zod": "1.8.0"
64
+ "@frontmcp/lazy-zod": "1.8.1"
65
65
  }
66
66
  }
@@ -1283,14 +1283,16 @@ function isNpmShellSource(source) {
1283
1283
  }
1284
1284
 
1285
1285
  // libs/uipack/src/shell/custom-shell-applier.ts
1286
+ var PLACEHOLDER_PATTERN = new RegExp(`\\{\\{(${SHELL_PLACEHOLDER_NAMES.join("|")})\\}\\}`, "g");
1287
+ var VALUE_KEYS = {
1288
+ CSP: "csp",
1289
+ DATA: "data",
1290
+ BRIDGE: "bridge",
1291
+ CONTENT: "content",
1292
+ TITLE: "title"
1293
+ };
1286
1294
  function applyShellTemplate(template, values) {
1287
- let result = template;
1288
- result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
1289
- result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
1290
- result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
1291
- result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
1292
- result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
1293
- return result;
1295
+ return template.replace(PLACEHOLDER_PATTERN, (_token, name) => values[VALUE_KEYS[name]]);
1294
1296
  }
1295
1297
 
1296
1298
  // libs/uipack/src/shell/custom-shell-validator.ts
@@ -1317,8 +1319,9 @@ function escapeHtml(str) {
1317
1319
  const s = String(str);
1318
1320
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
1319
1321
  }
1320
- function escapeScriptClose(jsonString) {
1321
- return jsonString.replace(/<\//g, "<\\/");
1322
+ var SCRIPT_UNSAFE_CHARACTERS = /[<>&\u2028\u2029]/g;
1323
+ function toUnicodeEscape(character) {
1324
+ return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`;
1322
1325
  }
1323
1326
  function safeJsonForScript(value) {
1324
1327
  if (value === void 0) {
@@ -1334,7 +1337,7 @@ function safeJsonForScript(value) {
1334
1337
  if (jsonString === void 0) {
1335
1338
  return "null";
1336
1339
  }
1337
- return escapeScriptClose(jsonString);
1340
+ return jsonString.replace(SCRIPT_UNSAFE_CHARACTERS, toUnicodeEscape);
1338
1341
  } catch {
1339
1342
  return '{"error":"Value could not be serialized"}';
1340
1343
  }
@@ -1393,9 +1396,7 @@ function createTemplateHelpers() {
1393
1396
  uniqueId: (prefix = "mcp") => {
1394
1397
  return `${prefix}-${++_uniqueIdCounter}`;
1395
1398
  },
1396
- jsonEmbed: (data) => {
1397
- return escapeScriptClose(JSON.stringify(data));
1398
- }
1399
+ jsonEmbed: (data) => safeJsonForScript(data)
1399
1400
  };
1400
1401
  }
1401
1402
 
@@ -33,6 +33,10 @@ function escapeJsString(str) {
33
33
  function escapeScriptClose(jsonString) {
34
34
  return jsonString.replace(/<\//g, "<\\/");
35
35
  }
36
+ var SCRIPT_UNSAFE_CHARACTERS = /[<>&\u2028\u2029]/g;
37
+ function toUnicodeEscape(character) {
38
+ return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`;
39
+ }
36
40
  function safeJsonForScript(value) {
37
41
  if (value === void 0) {
38
42
  return "null";
@@ -47,7 +51,7 @@ function safeJsonForScript(value) {
47
51
  if (jsonString === void 0) {
48
52
  return "null";
49
53
  }
50
- return escapeScriptClose(jsonString);
54
+ return jsonString.replace(SCRIPT_UNSAFE_CHARACTERS, toUnicodeEscape);
51
55
  } catch {
52
56
  return '{"error":"Value could not be serialized"}';
53
57
  }
package/index.js CHANGED
@@ -224,6 +224,10 @@ function escapeJsString(str) {
224
224
  function escapeScriptClose(jsonString) {
225
225
  return jsonString.replace(/<\//g, "<\\/");
226
226
  }
227
+ var SCRIPT_UNSAFE_CHARACTERS = /[<>&\u2028\u2029]/g;
228
+ function toUnicodeEscape(character) {
229
+ return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`;
230
+ }
227
231
  function safeJsonForScript(value) {
228
232
  if (value === void 0) {
229
233
  return "null";
@@ -238,7 +242,7 @@ function safeJsonForScript(value) {
238
242
  if (jsonString === void 0) {
239
243
  return "null";
240
244
  }
241
- return escapeScriptClose(jsonString);
245
+ return jsonString.replace(SCRIPT_UNSAFE_CHARACTERS, toUnicodeEscape);
242
246
  } catch {
243
247
  return '{"error":"Value could not be serialized"}';
244
248
  }
@@ -3002,14 +3006,16 @@ function isNpmShellSource(source) {
3002
3006
  }
3003
3007
 
3004
3008
  // libs/uipack/src/shell/custom-shell-applier.ts
3009
+ var PLACEHOLDER_PATTERN = new RegExp(`\\{\\{(${SHELL_PLACEHOLDER_NAMES.join("|")})\\}\\}`, "g");
3010
+ var VALUE_KEYS = {
3011
+ CSP: "csp",
3012
+ DATA: "data",
3013
+ BRIDGE: "bridge",
3014
+ CONTENT: "content",
3015
+ TITLE: "title"
3016
+ };
3005
3017
  function applyShellTemplate(template, values) {
3006
- let result = template;
3007
- result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
3008
- result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
3009
- result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
3010
- result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
3011
- result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
3012
- return result;
3018
+ return template.replace(PLACEHOLDER_PATTERN, (_token, name) => values[VALUE_KEYS[name]]);
3013
3019
  }
3014
3020
 
3015
3021
  // libs/uipack/src/shell/custom-shell-validator.ts
@@ -3081,9 +3087,7 @@ function createTemplateHelpers() {
3081
3087
  uniqueId: (prefix = "mcp") => {
3082
3088
  return `${prefix}-${++_uniqueIdCounter}`;
3083
3089
  },
3084
- jsonEmbed: (data) => {
3085
- return escapeScriptClose(JSON.stringify(data));
3086
- }
3090
+ jsonEmbed: (data) => safeJsonForScript(data)
3087
3091
  };
3088
3092
  }
3089
3093
 
@@ -3938,6 +3942,7 @@ var MERMAID_PREFIXES = [
3938
3942
  "xychart"
3939
3943
  ];
3940
3944
  var PDF_MAGIC = "JVBERi";
3945
+ var BASE64_PATTERN = /^[A-Za-z0-9+/=\r\n]+$/;
3941
3946
  function detectContentType(value) {
3942
3947
  if (value === null || value === void 0) {
3943
3948
  return "text";
@@ -3952,7 +3957,7 @@ function detectContentType(value) {
3952
3957
  if (typeof value === "string") {
3953
3958
  const trimmed = value.trimStart();
3954
3959
  if (trimmed.startsWith(PDF_MAGIC)) {
3955
- return "pdf";
3960
+ return BASE64_PATTERN.test(trimmed.trimEnd()) ? "pdf" : "text";
3956
3961
  }
3957
3962
  for (const prefix of MERMAID_PREFIXES) {
3958
3963
  if (trimmed.startsWith(prefix)) {
@@ -4014,7 +4019,7 @@ function buildPdfHtml(base64) {
4014
4019
  import * as pdfjsLib from 'https://esm.sh/pdfjs-dist@4/build/pdf.min.mjs';
4015
4020
  pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://esm.sh/pdfjs-dist@4/build/pdf.worker.min.mjs';
4016
4021
 
4017
- const base64 = ${JSON.stringify(base64)};
4022
+ const base64 = ${safeJsonForScript(base64)};
4018
4023
  const binaryStr = atob(base64);
4019
4024
  const bytes = new Uint8Array(binaryStr.length);
4020
4025
  for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
@@ -4109,6 +4114,12 @@ function buildCspConfig(resolver) {
4109
4114
  }
4110
4115
  return { resourceDomains: cspResourceDomains, connectDomains: cspConnectDomains };
4111
4116
  }
4117
+ function renderUnwrappedResult(rawResult) {
4118
+ if (typeof rawResult !== "string") {
4119
+ return `<pre>${escapeHtml(JSON.stringify(rawResult, null, 2))}</pre>`;
4120
+ }
4121
+ return detectContentType(rawResult) === "html" ? rawResult : escapeHtml(rawResult);
4122
+ }
4112
4123
  function renderToolTemplate(options) {
4113
4124
  const { toolName, input, output, template, resolver, platformType, sizing } = options;
4114
4125
  const uiType = detectUIType(template);
@@ -4147,8 +4158,7 @@ function renderToolTemplate(options) {
4147
4158
  if (wrapped) {
4148
4159
  html = wrapped;
4149
4160
  } else {
4150
- const textContent = typeof rawResult === "string" ? rawResult : `<pre>${JSON.stringify(rawResult, null, 2)}</pre>`;
4151
- const shellResult = buildShell(textContent, shellConfig);
4161
+ const shellResult = buildShell(renderUnwrappedResult(rawResult), shellConfig);
4152
4162
  html = shellResult.html;
4153
4163
  hash = shellResult.hash;
4154
4164
  size = shellResult.size;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/uipack",
3
- "version": "1.8.0",
3
+ "version": "1.8.1",
4
4
  "description": "FrontMCP UIpack - HTML shell builder, pluggable import resolver, and NPM component loader for MCP UI (React-free core)",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -61,6 +61,6 @@
61
61
  "typescript": "^5.9.3"
62
62
  },
63
63
  "peerDependencies": {
64
- "@frontmcp/lazy-zod": "1.8.0"
64
+ "@frontmcp/lazy-zod": "1.8.1"
65
65
  }
66
66
  }
@@ -4,18 +4,19 @@
4
4
  * Replaces placeholder tokens in a custom shell template with
5
5
  * the generated fragment values (CSP, data script, bridge, content, title).
6
6
  *
7
- * Uses sequential `replaceAll` — injected content is NOT re-scanned,
8
- * so `{{CSP}}` inside user content is safe.
7
+ * All placeholders are replaced in a single pass over the template, so an
8
+ * inserted value is never re-scanned for placeholders.
9
9
  *
10
10
  * @packageDocumentation
11
11
  */
12
- import type { ShellPlaceholderValues } from './custom-shell-types';
12
+ import { type ShellPlaceholderValues } from './custom-shell-types';
13
13
  /**
14
14
  * Apply placeholder values to a custom shell template.
15
15
  *
16
- * Each `{{PLACEHOLDER}}` token is replaced with the corresponding value.
17
- * Replacement is single-pass per token — values injected for one placeholder
18
- * are not re-scanned for other placeholders.
16
+ * Each `{{PLACEHOLDER}}` token is replaced with the corresponding value, verbatim.
17
+ * Values carry tool input and output (GHSA-rhr9-vhpf-jqp7), so they are inserted by a
18
+ * function replacer: a value is never re-scanned for other placeholders, and `$&`, `$'`
19
+ * or `` $` `` inside it are not expanded.
19
20
  *
20
21
  * @param template - The shell template with `{{PLACEHOLDER}}` tokens
21
22
  * @param values - The values to inject for each placeholder
@@ -1 +1 @@
1
- {"version":3,"file":"custom-shell-applier.d.ts","sourceRoot":"","sources":["../../src/shell/custom-shell-applier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAEnE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAU3F"}
1
+ {"version":3,"file":"custom-shell-applier.d.ts","sourceRoot":"","sources":["../../src/shell/custom-shell-applier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAsD,KAAK,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAYvH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,sBAAsB,GAAG,MAAM,CAE3F"}
@@ -1 +1 @@
1
- {"version":3,"file":"data-injector.d.ts","sourceRoot":"","sources":["../../src/shell/data-injector.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,MAAM,IAAI,YAAY,CASvE;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB,GAAG,MAAM,CAgBT;AAED;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,UAAU,EAAE;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAeT;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IACrC,gCAAgC;IAChC,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7D,kCAAkC;IAClC,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9D,4CAA4C;IAC5C,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACtC,qCAAqC;IACrC,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,CAAC;CACtC;AAID;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CA6BvD"}
1
+ {"version":3,"file":"data-injector.d.ts","sourceRoot":"","sources":["../../src/shell/data-injector.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,MAAM,IAAI,YAAY,CASvE;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB,GAAG,MAAM,CAgBT;AAED;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAAC,UAAU,EAAE;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAeT;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IACrC,gCAAgC;IAChC,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7D,kCAAkC;IAClC,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9D,4CAA4C;IAC5C,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACtC,qCAAqC;IACrC,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,CAAC;CACtC;AAID;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CA2BvD"}
package/shell/index.js CHANGED
@@ -1332,14 +1332,16 @@ function isNpmShellSource(source) {
1332
1332
  }
1333
1333
 
1334
1334
  // libs/uipack/src/shell/custom-shell-applier.ts
1335
+ var PLACEHOLDER_PATTERN = new RegExp(`\\{\\{(${SHELL_PLACEHOLDER_NAMES.join("|")})\\}\\}`, "g");
1336
+ var VALUE_KEYS = {
1337
+ CSP: "csp",
1338
+ DATA: "data",
1339
+ BRIDGE: "bridge",
1340
+ CONTENT: "content",
1341
+ TITLE: "title"
1342
+ };
1335
1343
  function applyShellTemplate(template, values) {
1336
- let result = template;
1337
- result = result.replaceAll(SHELL_PLACEHOLDERS.CSP, values.csp);
1338
- result = result.replaceAll(SHELL_PLACEHOLDERS.DATA, values.data);
1339
- result = result.replaceAll(SHELL_PLACEHOLDERS.BRIDGE, values.bridge);
1340
- result = result.replaceAll(SHELL_PLACEHOLDERS.CONTENT, values.content);
1341
- result = result.replaceAll(SHELL_PLACEHOLDERS.TITLE, values.title);
1342
- return result;
1344
+ return template.replace(PLACEHOLDER_PATTERN, (_token, name) => values[VALUE_KEYS[name]]);
1343
1345
  }
1344
1346
 
1345
1347
  // libs/uipack/src/shell/custom-shell-validator.ts
@@ -1366,8 +1368,9 @@ function escapeHtml(str) {
1366
1368
  const s = String(str);
1367
1369
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
1368
1370
  }
1369
- function escapeScriptClose(jsonString) {
1370
- return jsonString.replace(/<\//g, "<\\/");
1371
+ var SCRIPT_UNSAFE_CHARACTERS = /[<>&\u2028\u2029]/g;
1372
+ function toUnicodeEscape(character) {
1373
+ return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`;
1371
1374
  }
1372
1375
  function safeJsonForScript(value) {
1373
1376
  if (value === void 0) {
@@ -1383,7 +1386,7 @@ function safeJsonForScript(value) {
1383
1386
  if (jsonString === void 0) {
1384
1387
  return "null";
1385
1388
  }
1386
- return escapeScriptClose(jsonString);
1389
+ return jsonString.replace(SCRIPT_UNSAFE_CHARACTERS, toUnicodeEscape);
1387
1390
  } catch {
1388
1391
  return '{"error":"Value could not be serialized"}';
1389
1392
  }
@@ -1442,9 +1445,7 @@ function createTemplateHelpers() {
1442
1445
  uniqueId: (prefix = "mcp") => {
1443
1446
  return `${prefix}-${++_uniqueIdCounter}`;
1444
1447
  },
1445
- jsonEmbed: (data) => {
1446
- return escapeScriptClose(JSON.stringify(data));
1447
- }
1448
+ jsonEmbed: (data) => safeJsonForScript(data)
1448
1449
  };
1449
1450
  }
1450
1451
 
package/utils/index.d.ts CHANGED
@@ -11,5 +11,12 @@ export declare function escapeHtml(str: unknown): string;
11
11
  export declare function escapeHtmlAttr(str: string): string;
12
12
  export declare function escapeJsString(str: string): string;
13
13
  export declare function escapeScriptClose(jsonString: string): string;
14
+ /**
15
+ * Serialize a value as JSON that can be embedded in an inline `<script>`.
16
+ *
17
+ * `<`, `>`, `&`, U+2028 and U+2029 are written as `\uXXXX`, so the output can neither close
18
+ * the element, open a comment or nested script, nor end a line; `JSON.parse` and the JS engine
19
+ * read the escapes back as the original characters.
20
+ */
14
21
  export declare function safeJsonForScript(value: unknown): string;
15
22
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAiBpE;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAc/C;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAUlD;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAqBxD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAiBpE;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAc/C;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAUlD;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE5D;AAQD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAqBxD"}
package/utils/index.js CHANGED
@@ -62,6 +62,10 @@ function escapeJsString(str) {
62
62
  function escapeScriptClose(jsonString) {
63
63
  return jsonString.replace(/<\//g, "<\\/");
64
64
  }
65
+ var SCRIPT_UNSAFE_CHARACTERS = /[<>&\u2028\u2029]/g;
66
+ function toUnicodeEscape(character) {
67
+ return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`;
68
+ }
65
69
  function safeJsonForScript(value) {
66
70
  if (value === void 0) {
67
71
  return "null";
@@ -76,7 +80,7 @@ function safeJsonForScript(value) {
76
80
  if (jsonString === void 0) {
77
81
  return "null";
78
82
  }
79
- return escapeScriptClose(jsonString);
83
+ return jsonString.replace(SCRIPT_UNSAFE_CHARACTERS, toUnicodeEscape);
80
84
  } catch {
81
85
  return '{"error":"Value could not be serialized"}';
82
86
  }