@seed-ship/mcp-ui-solid 6.20.0 → 6.21.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.
- package/CHANGELOG.md +144 -0
- package/README.md +120 -3
- package/dist/components/IframeFallbackLink.cjs +37 -0
- package/dist/components/IframeFallbackLink.cjs.map +1 -0
- package/dist/components/IframeFallbackLink.d.ts +23 -0
- package/dist/components/IframeFallbackLink.d.ts.map +1 -0
- package/dist/components/IframeFallbackLink.js +37 -0
- package/dist/components/IframeFallbackLink.js.map +1 -0
- package/dist/components/UIResourceRenderer.cjs +150 -127
- package/dist/components/UIResourceRenderer.cjs.map +1 -1
- package/dist/components/UIResourceRenderer.d.ts.map +1 -1
- package/dist/components/UIResourceRenderer.js +151 -128
- package/dist/components/UIResourceRenderer.js.map +1 -1
- package/dist/components/VideoRenderer.cjs +45 -19
- package/dist/components/VideoRenderer.cjs.map +1 -1
- package/dist/components/VideoRenderer.d.ts.map +1 -1
- package/dist/components/VideoRenderer.js +45 -19
- package/dist/components/VideoRenderer.js.map +1 -1
- package/dist/context/MCPUIConfigContext.cjs +29 -0
- package/dist/context/MCPUIConfigContext.cjs.map +1 -0
- package/dist/context/MCPUIConfigContext.d.ts +124 -0
- package/dist/context/MCPUIConfigContext.d.ts.map +1 -0
- package/dist/context/MCPUIConfigContext.js +29 -0
- package/dist/context/MCPUIConfigContext.js.map +1 -0
- package/dist/context/MCPUIStringsContext.cjs +3 -1
- package/dist/context/MCPUIStringsContext.cjs.map +1 -1
- package/dist/context/MCPUIStringsContext.d.ts +7 -0
- package/dist/context/MCPUIStringsContext.d.ts.map +1 -1
- package/dist/context/MCPUIStringsContext.js +3 -1
- package/dist/context/MCPUIStringsContext.js.map +1 -1
- package/dist/index.cjs +6 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/validation.cjs +11 -10
- package/dist/services/validation.cjs.map +1 -1
- package/dist/services/validation.d.ts +20 -0
- package/dist/services/validation.d.ts.map +1 -1
- package/dist/services/validation.js +11 -10
- package/dist/services/validation.js.map +1 -1
- package/dist/utils/iframe-coep.cjs +30 -0
- package/dist/utils/iframe-coep.cjs.map +1 -0
- package/dist/utils/iframe-coep.d.ts +46 -0
- package/dist/utils/iframe-coep.d.ts.map +1 -0
- package/dist/utils/iframe-coep.js +30 -0
- package/dist/utils/iframe-coep.js.map +1 -0
- package/dist/validation.cjs +2 -0
- package/dist/validation.cjs.map +1 -1
- package/dist/validation.d.cts +1 -1
- package/dist/validation.d.ts +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +3 -1
- package/package.json +1 -1
- package/src/components/IframeFallbackLink.tsx +57 -0
- package/src/components/UIResourceRenderer.tsx +31 -4
- package/src/components/VideoRenderer.tsx +20 -0
- package/src/context/MCPUIConfigContext.tsx +151 -0
- package/src/context/MCPUIStringsContext.tsx +10 -0
- package/src/index.ts +15 -0
- package/src/services/index.ts +1 -0
- package/src/services/validation.ts +38 -16
- package/src/solid-jsx-attrs.d.ts +24 -0
- package/src/utils/iframe-coep.ts +74 -0
- package/src/validation.ts +4 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const solidJs = require("solid-js");
|
|
4
|
+
const validation = require("../services/validation.cjs");
|
|
5
|
+
function shouldSetCredentialless(url, config, options) {
|
|
6
|
+
const mode = config.iframeCredentialless;
|
|
7
|
+
if (mode === "always") return true;
|
|
8
|
+
if (mode === "never") return false;
|
|
9
|
+
if (!options.sandboxedWithoutSameOrigin) return false;
|
|
10
|
+
return !validation.isTrustedIframeDomain(url, {
|
|
11
|
+
customTrustedDomains: config.customTrustedIframeDomains
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function createCrossOriginIsolated() {
|
|
15
|
+
const [isolated, setIsolated] = solidJs.createSignal(false);
|
|
16
|
+
solidJs.onMount(() => {
|
|
17
|
+
setIsolated(typeof window !== "undefined" && window.crossOriginIsolated === true);
|
|
18
|
+
});
|
|
19
|
+
return isolated;
|
|
20
|
+
}
|
|
21
|
+
function shouldShowIframeFallbackLink(config, crossOriginIsolated) {
|
|
22
|
+
const mode = config.iframeFallbackLink;
|
|
23
|
+
if (mode === "always") return true;
|
|
24
|
+
if (mode === "never") return false;
|
|
25
|
+
return crossOriginIsolated;
|
|
26
|
+
}
|
|
27
|
+
exports.createCrossOriginIsolated = createCrossOriginIsolated;
|
|
28
|
+
exports.shouldSetCredentialless = shouldSetCredentialless;
|
|
29
|
+
exports.shouldShowIframeFallbackLink = shouldShowIframeFallbackLink;
|
|
30
|
+
//# sourceMappingURL=iframe-coep.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iframe-coep.cjs","sources":["../../src/utils/iframe-coep.ts"],"sourcesContent":["/**\n * COEP helpers for the two iframes the library renders\n * (`IframeRenderer` in `UIResourceRenderer.tsx`, and the YouTube / Vimeo\n * embed in `VideoRenderer.tsx`).\n *\n * @since v6.21.0\n *\n * A host that serves `Cross-Origin-Embedder-Policy: credentialless` makes the\n * browser refuse any cross-origin iframe whose own document sends no COEP\n * header. The HTML attribute `credentialless` on the `<iframe>` lifts that,\n * at the price of the embedded document losing access to the cookies and\n * storage of its own origin — see `MCPUIConfigContext` for the policy.\n */\n\nimport { createSignal, onMount, type Accessor } from 'solid-js'\nimport { isTrustedIframeDomain } from '../services/validation'\nimport type { MCPUIConfig } from '../context/MCPUIConfigContext'\n\n/**\n * Does this iframe get the boolean `credentialless` attribute?\n *\n * `'auto'` (the default) says yes only when the embed has nothing to lose:\n * the host is not trusted AND the iframe already runs without cookies because\n * its `sandbox` has no `allow-same-origin` (`sandboxedWithoutSameOrigin`).\n * The video embed carries no sandbox, so in `'auto'` it keeps its provider\n * cookies and a COEP host must opt in with `'always'`.\n *\n * An unparsable URL is not trusted, so it gets the attribute.\n */\nexport function shouldSetCredentialless(\n url: string,\n config: MCPUIConfig,\n options: { sandboxedWithoutSameOrigin: boolean }\n): boolean {\n const mode = config.iframeCredentialless\n if (mode === 'always') return true\n if (mode === 'never') return false\n if (!options.sandboxedWithoutSameOrigin) return false\n return !isTrustedIframeDomain(url, {\n customTrustedDomains: config.customTrustedIframeDomains,\n })\n}\n\n/**\n * `window.crossOriginIsolated`, read **after mount** into a signal.\n *\n * Reading it during render would make the server emit markup the client\n * cannot reproduce; the signal starts at `false` (the SSR value) and flips\n * once mounted, so hydration always matches.\n */\nexport function createCrossOriginIsolated(): Accessor<boolean> {\n const [isolated, setIsolated] = createSignal(false)\n onMount(() => {\n setIsolated(typeof window !== 'undefined' && window.crossOriginIsolated === true)\n })\n return isolated\n}\n\n/**\n * Does the \"open in a new tab\" link show under an embed?\n *\n * `'auto'` shows it only on a cross-origin-isolated page — exactly where COEP\n * can block the frame, and where the blocking is silent (a refused\n * cross-origin document fires no `error` event the parent can observe).\n */\nexport function shouldShowIframeFallbackLink(\n config: MCPUIConfig,\n crossOriginIsolated: boolean\n): boolean {\n const mode = config.iframeFallbackLink\n if (mode === 'always') return true\n if (mode === 'never') return false\n return crossOriginIsolated\n}\n"],"names":["isTrustedIframeDomain","createSignal","onMount"],"mappings":";;;;AA6BO,SAAS,wBACd,KACA,QACA,SACS;AACT,QAAM,OAAO,OAAO;AACpB,MAAI,SAAS,SAAU,QAAO;AAC9B,MAAI,SAAS,QAAS,QAAO;AAC7B,MAAI,CAAC,QAAQ,2BAA4B,QAAO;AAChD,SAAO,CAACA,WAAAA,sBAAsB,KAAK;AAAA,IACjC,sBAAsB,OAAO;AAAA,EAAA,CAC9B;AACH;AASO,SAAS,4BAA+C;AAC7D,QAAM,CAAC,UAAU,WAAW,IAAIC,QAAAA,aAAa,KAAK;AAClDC,UAAAA,QAAQ,MAAM;AACZ,gBAAY,OAAO,WAAW,eAAe,OAAO,wBAAwB,IAAI;AAAA,EAClF,CAAC;AACD,SAAO;AACT;AASO,SAAS,6BACd,QACA,qBACS;AACT,QAAM,OAAO,OAAO;AACpB,MAAI,SAAS,SAAU,QAAO;AAC9B,MAAI,SAAS,QAAS,QAAO;AAC7B,SAAO;AACT;;;;"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* COEP helpers for the two iframes the library renders
|
|
3
|
+
* (`IframeRenderer` in `UIResourceRenderer.tsx`, and the YouTube / Vimeo
|
|
4
|
+
* embed in `VideoRenderer.tsx`).
|
|
5
|
+
*
|
|
6
|
+
* @since v6.21.0
|
|
7
|
+
*
|
|
8
|
+
* A host that serves `Cross-Origin-Embedder-Policy: credentialless` makes the
|
|
9
|
+
* browser refuse any cross-origin iframe whose own document sends no COEP
|
|
10
|
+
* header. The HTML attribute `credentialless` on the `<iframe>` lifts that,
|
|
11
|
+
* at the price of the embedded document losing access to the cookies and
|
|
12
|
+
* storage of its own origin — see `MCPUIConfigContext` for the policy.
|
|
13
|
+
*/
|
|
14
|
+
import { type Accessor } from 'solid-js';
|
|
15
|
+
import type { MCPUIConfig } from '../context/MCPUIConfigContext';
|
|
16
|
+
/**
|
|
17
|
+
* Does this iframe get the boolean `credentialless` attribute?
|
|
18
|
+
*
|
|
19
|
+
* `'auto'` (the default) says yes only when the embed has nothing to lose:
|
|
20
|
+
* the host is not trusted AND the iframe already runs without cookies because
|
|
21
|
+
* its `sandbox` has no `allow-same-origin` (`sandboxedWithoutSameOrigin`).
|
|
22
|
+
* The video embed carries no sandbox, so in `'auto'` it keeps its provider
|
|
23
|
+
* cookies and a COEP host must opt in with `'always'`.
|
|
24
|
+
*
|
|
25
|
+
* An unparsable URL is not trusted, so it gets the attribute.
|
|
26
|
+
*/
|
|
27
|
+
export declare function shouldSetCredentialless(url: string, config: MCPUIConfig, options: {
|
|
28
|
+
sandboxedWithoutSameOrigin: boolean;
|
|
29
|
+
}): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* `window.crossOriginIsolated`, read **after mount** into a signal.
|
|
32
|
+
*
|
|
33
|
+
* Reading it during render would make the server emit markup the client
|
|
34
|
+
* cannot reproduce; the signal starts at `false` (the SSR value) and flips
|
|
35
|
+
* once mounted, so hydration always matches.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createCrossOriginIsolated(): Accessor<boolean>;
|
|
38
|
+
/**
|
|
39
|
+
* Does the "open in a new tab" link show under an embed?
|
|
40
|
+
*
|
|
41
|
+
* `'auto'` shows it only on a cross-origin-isolated page — exactly where COEP
|
|
42
|
+
* can block the frame, and where the blocking is silent (a refused
|
|
43
|
+
* cross-origin document fires no `error` event the parent can observe).
|
|
44
|
+
*/
|
|
45
|
+
export declare function shouldShowIframeFallbackLink(config: MCPUIConfig, crossOriginIsolated: boolean): boolean;
|
|
46
|
+
//# sourceMappingURL=iframe-coep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iframe-coep.d.ts","sourceRoot":"","sources":["../../src/utils/iframe-coep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAyB,KAAK,QAAQ,EAAE,MAAM,UAAU,CAAA;AAE/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAA;AAEhE;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE;IAAE,0BAA0B,EAAE,OAAO,CAAA;CAAE,GAC/C,OAAO,CAQT;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,IAAI,QAAQ,CAAC,OAAO,CAAC,CAM7D;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,WAAW,EACnB,mBAAmB,EAAE,OAAO,GAC3B,OAAO,CAKT"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createSignal, onMount } from "solid-js";
|
|
2
|
+
import { isTrustedIframeDomain } from "../services/validation.js";
|
|
3
|
+
function shouldSetCredentialless(url, config, options) {
|
|
4
|
+
const mode = config.iframeCredentialless;
|
|
5
|
+
if (mode === "always") return true;
|
|
6
|
+
if (mode === "never") return false;
|
|
7
|
+
if (!options.sandboxedWithoutSameOrigin) return false;
|
|
8
|
+
return !isTrustedIframeDomain(url, {
|
|
9
|
+
customTrustedDomains: config.customTrustedIframeDomains
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
function createCrossOriginIsolated() {
|
|
13
|
+
const [isolated, setIsolated] = createSignal(false);
|
|
14
|
+
onMount(() => {
|
|
15
|
+
setIsolated(typeof window !== "undefined" && window.crossOriginIsolated === true);
|
|
16
|
+
});
|
|
17
|
+
return isolated;
|
|
18
|
+
}
|
|
19
|
+
function shouldShowIframeFallbackLink(config, crossOriginIsolated) {
|
|
20
|
+
const mode = config.iframeFallbackLink;
|
|
21
|
+
if (mode === "always") return true;
|
|
22
|
+
if (mode === "never") return false;
|
|
23
|
+
return crossOriginIsolated;
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
createCrossOriginIsolated,
|
|
27
|
+
shouldSetCredentialless,
|
|
28
|
+
shouldShowIframeFallbackLink
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=iframe-coep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iframe-coep.js","sources":["../../src/utils/iframe-coep.ts"],"sourcesContent":["/**\n * COEP helpers for the two iframes the library renders\n * (`IframeRenderer` in `UIResourceRenderer.tsx`, and the YouTube / Vimeo\n * embed in `VideoRenderer.tsx`).\n *\n * @since v6.21.0\n *\n * A host that serves `Cross-Origin-Embedder-Policy: credentialless` makes the\n * browser refuse any cross-origin iframe whose own document sends no COEP\n * header. The HTML attribute `credentialless` on the `<iframe>` lifts that,\n * at the price of the embedded document losing access to the cookies and\n * storage of its own origin — see `MCPUIConfigContext` for the policy.\n */\n\nimport { createSignal, onMount, type Accessor } from 'solid-js'\nimport { isTrustedIframeDomain } from '../services/validation'\nimport type { MCPUIConfig } from '../context/MCPUIConfigContext'\n\n/**\n * Does this iframe get the boolean `credentialless` attribute?\n *\n * `'auto'` (the default) says yes only when the embed has nothing to lose:\n * the host is not trusted AND the iframe already runs without cookies because\n * its `sandbox` has no `allow-same-origin` (`sandboxedWithoutSameOrigin`).\n * The video embed carries no sandbox, so in `'auto'` it keeps its provider\n * cookies and a COEP host must opt in with `'always'`.\n *\n * An unparsable URL is not trusted, so it gets the attribute.\n */\nexport function shouldSetCredentialless(\n url: string,\n config: MCPUIConfig,\n options: { sandboxedWithoutSameOrigin: boolean }\n): boolean {\n const mode = config.iframeCredentialless\n if (mode === 'always') return true\n if (mode === 'never') return false\n if (!options.sandboxedWithoutSameOrigin) return false\n return !isTrustedIframeDomain(url, {\n customTrustedDomains: config.customTrustedIframeDomains,\n })\n}\n\n/**\n * `window.crossOriginIsolated`, read **after mount** into a signal.\n *\n * Reading it during render would make the server emit markup the client\n * cannot reproduce; the signal starts at `false` (the SSR value) and flips\n * once mounted, so hydration always matches.\n */\nexport function createCrossOriginIsolated(): Accessor<boolean> {\n const [isolated, setIsolated] = createSignal(false)\n onMount(() => {\n setIsolated(typeof window !== 'undefined' && window.crossOriginIsolated === true)\n })\n return isolated\n}\n\n/**\n * Does the \"open in a new tab\" link show under an embed?\n *\n * `'auto'` shows it only on a cross-origin-isolated page — exactly where COEP\n * can block the frame, and where the blocking is silent (a refused\n * cross-origin document fires no `error` event the parent can observe).\n */\nexport function shouldShowIframeFallbackLink(\n config: MCPUIConfig,\n crossOriginIsolated: boolean\n): boolean {\n const mode = config.iframeFallbackLink\n if (mode === 'always') return true\n if (mode === 'never') return false\n return crossOriginIsolated\n}\n"],"names":[],"mappings":";;AA6BO,SAAS,wBACd,KACA,QACA,SACS;AACT,QAAM,OAAO,OAAO;AACpB,MAAI,SAAS,SAAU,QAAO;AAC9B,MAAI,SAAS,QAAS,QAAO;AAC7B,MAAI,CAAC,QAAQ,2BAA4B,QAAO;AAChD,SAAO,CAAC,sBAAsB,KAAK;AAAA,IACjC,sBAAsB,OAAO;AAAA,EAAA,CAC9B;AACH;AASO,SAAS,4BAA+C;AAC7D,QAAM,CAAC,UAAU,WAAW,IAAI,aAAa,KAAK;AAClD,UAAQ,MAAM;AACZ,gBAAY,OAAO,WAAW,eAAe,OAAO,wBAAwB,IAAI;AAAA,EAClF,CAAC;AACD,SAAO;AACT;AASO,SAAS,6BACd,QACA,qBACS;AACT,QAAM,OAAO,OAAO;AACpB,MAAI,SAAS,SAAU,QAAO;AAC9B,MAAI,SAAS,QAAS,QAAO;AAC7B,SAAO;AACT;"}
|
package/dist/validation.cjs
CHANGED
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
3
3
|
const validation = require("./services/validation.cjs");
|
|
4
4
|
exports.DEFAULT_RESOURCE_LIMITS = validation.DEFAULT_RESOURCE_LIMITS;
|
|
5
5
|
exports.DEFAULT_VALIDATION_MESSAGES = validation.DEFAULT_VALIDATION_MESSAGES;
|
|
6
|
+
exports.getIframeSandbox = validation.getIframeSandbox;
|
|
7
|
+
exports.isTrustedIframeDomain = validation.isTrustedIframeDomain;
|
|
6
8
|
exports.sanitizeString = validation.sanitizeString;
|
|
7
9
|
exports.validateChartComponent = validation.validateChartComponent;
|
|
8
10
|
exports.validateComponent = validation.validateComponent;
|
package/dist/validation.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validation.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;"}
|
package/dist/validation.d.cts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* import { validateLayout } from '@seed-ship/mcp-ui-solid/validation'
|
|
10
10
|
* ```
|
|
11
11
|
*/
|
|
12
|
-
export { validateComponent, validateLayout, validateGridPosition, validateChartComponent, validateTableComponent, validatePayloadSize, validateIframeDomain, sanitizeString, DEFAULT_RESOURCE_LIMITS, validateFieldValue, validateFormData, DEFAULT_VALIDATION_MESSAGES, } from './services/validation';
|
|
12
|
+
export { validateComponent, validateLayout, validateGridPosition, validateChartComponent, validateTableComponent, validatePayloadSize, validateIframeDomain, getIframeSandbox, isTrustedIframeDomain, sanitizeString, DEFAULT_RESOURCE_LIMITS, validateFieldValue, validateFormData, DEFAULT_VALIDATION_MESSAGES, } from './services/validation';
|
|
13
13
|
export type { FormValidationMessages } from './services/validation';
|
|
14
14
|
export type { UIComponent, UILayout, GridPosition, ComponentType, ChartComponentParams, TableComponentParams, MetricComponentParams, TextComponentParams, ValidationResult, ResourceLimits, } from './types';
|
|
15
15
|
//# sourceMappingURL=validation.d.ts.map
|
package/dist/validation.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* import { validateLayout } from '@seed-ship/mcp-ui-solid/validation'
|
|
10
10
|
* ```
|
|
11
11
|
*/
|
|
12
|
-
export { validateComponent, validateLayout, validateGridPosition, validateChartComponent, validateTableComponent, validatePayloadSize, validateIframeDomain, sanitizeString, DEFAULT_RESOURCE_LIMITS, validateFieldValue, validateFormData, DEFAULT_VALIDATION_MESSAGES, } from './services/validation';
|
|
12
|
+
export { validateComponent, validateLayout, validateGridPosition, validateChartComponent, validateTableComponent, validatePayloadSize, validateIframeDomain, getIframeSandbox, isTrustedIframeDomain, sanitizeString, DEFAULT_RESOURCE_LIMITS, validateFieldValue, validateFormData, DEFAULT_VALIDATION_MESSAGES, } from './services/validation';
|
|
13
13
|
export type { FormValidationMessages } from './services/validation';
|
|
14
14
|
export type { UIComponent, UILayout, GridPosition, ComponentType, ChartComponentParams, TableComponentParams, MetricComponentParams, TextComponentParams, ValidationResult, ResourceLimits, } from './types';
|
|
15
15
|
//# sourceMappingURL=validation.d.ts.map
|
package/dist/validation.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,cAAc,EACd,uBAAuB,EAEvB,kBAAkB,EAClB,gBAAgB,EAChB,2BAA2B,GAC5B,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAGnE,YAAY,EACV,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,GACf,MAAM,SAAS,CAAA"}
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,gBAAgB,EAGhB,qBAAqB,EACrB,cAAc,EACd,uBAAuB,EAEvB,kBAAkB,EAClB,gBAAgB,EAChB,2BAA2B,GAC5B,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAGnE,YAAY,EACV,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,GACf,MAAM,SAAS,CAAA"}
|
package/dist/validation.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { DEFAULT_RESOURCE_LIMITS, DEFAULT_VALIDATION_MESSAGES, sanitizeString, validateChartComponent, validateComponent, validateFieldValue, validateFormData, validateGridPosition, validateIframeDomain, validateLayout, validatePayloadSize, validateTableComponent } from "./services/validation.js";
|
|
1
|
+
import { DEFAULT_RESOURCE_LIMITS, DEFAULT_VALIDATION_MESSAGES, getIframeSandbox, isTrustedIframeDomain, sanitizeString, validateChartComponent, validateComponent, validateFieldValue, validateFormData, validateGridPosition, validateIframeDomain, validateLayout, validatePayloadSize, validateTableComponent } from "./services/validation.js";
|
|
2
2
|
export {
|
|
3
3
|
DEFAULT_RESOURCE_LIMITS,
|
|
4
4
|
DEFAULT_VALIDATION_MESSAGES,
|
|
5
|
+
getIframeSandbox,
|
|
6
|
+
isTrustedIframeDomain,
|
|
5
7
|
sanitizeString,
|
|
6
8
|
validateChartComponent,
|
|
7
9
|
validateComponent,
|
package/package.json
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IframeFallbackLink — the way out of a silently blocked embed.
|
|
3
|
+
*
|
|
4
|
+
* @since v6.21.0
|
|
5
|
+
*
|
|
6
|
+
* An iframe refused by `Cross-Origin-Embedder-Policy` fires no event the
|
|
7
|
+
* parent page can observe (`onerror` does not fire for a rejected
|
|
8
|
+
* cross-origin document), so the user is left with a blank frame and no
|
|
9
|
+
* explanation. This link, rendered under the frame, always works.
|
|
10
|
+
*
|
|
11
|
+
* Visibility follows `MCPUIConfig.iframeFallbackLink`; `'auto'` (the default)
|
|
12
|
+
* shows it only on a cross-origin-isolated page. The URL goes through
|
|
13
|
+
* `safeUrl` — an unsafe one renders nothing at all.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { createMemo, Show, type JSX } from 'solid-js'
|
|
17
|
+
import { safeUrl } from '../utils/safe-url'
|
|
18
|
+
import { useMCPUIStrings } from '../context/MCPUIStringsContext'
|
|
19
|
+
import { useMCPUIConfig } from '../context/MCPUIConfigContext'
|
|
20
|
+
import {
|
|
21
|
+
createCrossOriginIsolated,
|
|
22
|
+
shouldShowIframeFallbackLink,
|
|
23
|
+
} from '../utils/iframe-coep'
|
|
24
|
+
|
|
25
|
+
export interface IframeFallbackLinkProps {
|
|
26
|
+
/** The embedded URL. Bound to `href` only when `safeUrl` accepts it. */
|
|
27
|
+
url: string
|
|
28
|
+
/** Extra classes on the wrapping `<div>`. */
|
|
29
|
+
class?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function IframeFallbackLink(props: IframeFallbackLinkProps): JSX.Element {
|
|
33
|
+
const strings = useMCPUIStrings()
|
|
34
|
+
const config = useMCPUIConfig()
|
|
35
|
+
const crossOriginIsolated = createCrossOriginIsolated()
|
|
36
|
+
|
|
37
|
+
const href = createMemo(() => safeUrl(props.url))
|
|
38
|
+
const visible = createMemo(
|
|
39
|
+
() => !!href() && shouldShowIframeFallbackLink(config, crossOriginIsolated())
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Show when={visible()}>
|
|
44
|
+
<div class={`px-4 py-2 text-right ${props.class || ''}`}>
|
|
45
|
+
<a
|
|
46
|
+
href={href()}
|
|
47
|
+
target="_blank"
|
|
48
|
+
rel="noopener noreferrer"
|
|
49
|
+
data-mcp-ui-action="open-external"
|
|
50
|
+
class="text-xs text-blue-600 dark:text-blue-400 hover:underline focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 rounded"
|
|
51
|
+
>
|
|
52
|
+
{strings.iframeOpenInNewTab}
|
|
53
|
+
</a>
|
|
54
|
+
</div>
|
|
55
|
+
</Show>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
@@ -10,7 +10,7 @@ import { escapeHtml } from '../utils/escape-html'
|
|
|
10
10
|
import { safeUrl } from '../utils/safe-url'
|
|
11
11
|
import { SafeHtml } from './SafeHtml'
|
|
12
12
|
import type { UIComponent, UILayout, RendererError, TableVirtualizeOptions } from '../types'
|
|
13
|
-
import { validateComponent, DEFAULT_RESOURCE_LIMITS, getIframeSandbox } from '../services/validation'
|
|
13
|
+
import { validateComponent, DEFAULT_RESOURCE_LIMITS, getIframeSandbox, isTrustedIframeDomain } from '../services/validation'
|
|
14
14
|
import { GenerativeUIErrorBoundary } from './GenerativeUIErrorBoundary'
|
|
15
15
|
import { markRenderStart, markRenderEnd, PERF_PREFIX } from '../utils/perf'
|
|
16
16
|
import { isDebugEnabled } from '../utils/logger'
|
|
@@ -27,6 +27,9 @@ import {
|
|
|
27
27
|
formatMCPUIString,
|
|
28
28
|
useMCPUIStrings,
|
|
29
29
|
} from '../context/MCPUIStringsContext'
|
|
30
|
+
import { useMCPUIConfig } from '../context/MCPUIConfigContext'
|
|
31
|
+
import { shouldSetCredentialless } from '../utils/iframe-coep'
|
|
32
|
+
import { IframeFallbackLink } from './IframeFallbackLink'
|
|
30
33
|
|
|
31
34
|
/**
|
|
32
35
|
* How `<UIResourceRenderer>` reacts when `validateComponent()` rejects a
|
|
@@ -1458,7 +1461,12 @@ function TextRenderer(props: { component: UIComponent }) {
|
|
|
1458
1461
|
*/
|
|
1459
1462
|
function IframeRenderer(props: { component: UIComponent }) {
|
|
1460
1463
|
const strings = useMCPUIStrings()
|
|
1464
|
+
const config = useMCPUIConfig()
|
|
1461
1465
|
const params = props.component.params as any
|
|
1466
|
+
// v6.21.0 — the host's custom trusted hosts now reach BOTH the sandbox (they
|
|
1467
|
+
// used not to: `getIframeSandbox(params.url)` was called with no options) and
|
|
1468
|
+
// the `credentialless` decision.
|
|
1469
|
+
const trustOptions = () => ({ customTrustedDomains: config.customTrustedIframeDomains })
|
|
1462
1470
|
return (
|
|
1463
1471
|
<div class="w-full h-full bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden flex flex-col">
|
|
1464
1472
|
<Show when={params.title}>
|
|
@@ -1471,9 +1479,22 @@ function IframeRenderer(props: { component: UIComponent }) {
|
|
|
1471
1479
|
title={params.title || strings.iframeTitle}
|
|
1472
1480
|
class="w-full border-0 flex-1"
|
|
1473
1481
|
style={`height: ${params.height || '400px'}; min-height: 300px;`}
|
|
1474
|
-
sandbox={getIframeSandbox(params.url)}
|
|
1482
|
+
sandbox={getIframeSandbox(params.url, trustOptions())}
|
|
1483
|
+
// `attr:` is required: `credentialless` is in dom-expressions'
|
|
1484
|
+
// `Properties` set, so a plain `credentialless={…}` compiles to a JS
|
|
1485
|
+
// property assignment and never reaches the DOM as an attribute.
|
|
1486
|
+
// `true | undefined` keeps it a BOOLEAN attribute — bare in SSR
|
|
1487
|
+
// markup, removed (not `="false"`) when it does not apply.
|
|
1488
|
+
attr:credentialless={
|
|
1489
|
+
shouldSetCredentialless(params.url, config, {
|
|
1490
|
+
// This iframe's sandbox never carries `allow-same-origin` for an
|
|
1491
|
+
// untrusted host, so it already runs without cookies.
|
|
1492
|
+
sandboxedWithoutSameOrigin: !isTrustedIframeDomain(params.url, trustOptions()),
|
|
1493
|
+
}) || undefined
|
|
1494
|
+
}
|
|
1475
1495
|
loading="lazy"
|
|
1476
1496
|
/>
|
|
1497
|
+
<IframeFallbackLink url={params.url} class="border-t border-gray-200 dark:border-gray-700" />
|
|
1477
1498
|
</div>
|
|
1478
1499
|
)
|
|
1479
1500
|
}
|
|
@@ -1595,6 +1616,7 @@ function ComponentRenderer(props: {
|
|
|
1595
1616
|
// opt in see zero behavior change.
|
|
1596
1617
|
const telemetry = useTelemetry()
|
|
1597
1618
|
const strings = useMCPUIStrings()
|
|
1619
|
+
const componentConfig = useMCPUIConfig()
|
|
1598
1620
|
|
|
1599
1621
|
onMount(() => {
|
|
1600
1622
|
markRenderEnd(props.component.id)
|
|
@@ -1636,8 +1658,13 @@ function ComponentRenderer(props: {
|
|
|
1636
1658
|
}
|
|
1637
1659
|
})
|
|
1638
1660
|
|
|
1639
|
-
// Validate component before rendering
|
|
1640
|
-
|
|
1661
|
+
// Validate component before rendering. The host's allow-list extension
|
|
1662
|
+
// (v6.21.0) has to reach validation too: an unlisted host is replaced by the
|
|
1663
|
+
// validation card before any renderer runs.
|
|
1664
|
+
const validation = validateComponent(props.component, {
|
|
1665
|
+
iframePolicy: componentConfig.iframePolicy,
|
|
1666
|
+
customIframeDomains: componentConfig.customIframeDomains,
|
|
1667
|
+
})
|
|
1641
1668
|
if (!validation.valid) {
|
|
1642
1669
|
props.onError?.({
|
|
1643
1670
|
type: 'validation',
|
|
@@ -9,6 +9,9 @@ import { Component, createMemo, Show } from 'solid-js'
|
|
|
9
9
|
import type { UIComponent, VideoComponentParams } from '../types'
|
|
10
10
|
import { ExpandableWrapper, useExpanded } from './ExpandableWrapper'
|
|
11
11
|
import { useMCPUIStrings } from '../context/MCPUIStringsContext'
|
|
12
|
+
import { useMCPUIConfig } from '../context/MCPUIConfigContext'
|
|
13
|
+
import { shouldSetCredentialless } from '../utils/iframe-coep'
|
|
14
|
+
import { IframeFallbackLink } from './IframeFallbackLink'
|
|
12
15
|
|
|
13
16
|
export interface VideoRendererProps {
|
|
14
17
|
/**
|
|
@@ -76,6 +79,7 @@ function parseVideoUrl(url: string): VideoInfo {
|
|
|
76
79
|
|
|
77
80
|
export const VideoRenderer: Component<VideoRendererProps> = (props) => {
|
|
78
81
|
const strings = useMCPUIStrings()
|
|
82
|
+
const config = useMCPUIConfig()
|
|
79
83
|
const params = () => props.params || (props.component?.params as VideoComponentParams)
|
|
80
84
|
const isExpanded = useExpanded()
|
|
81
85
|
|
|
@@ -181,11 +185,27 @@ export const VideoRenderer: Component<VideoRendererProps> = (props) => {
|
|
|
181
185
|
class="absolute inset-0 w-full h-full"
|
|
182
186
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
183
187
|
allowfullscreen
|
|
188
|
+
// v6.21.0 — youtube-nocookie / player.vimeo are not trusted hosts,
|
|
189
|
+
// so under COEP `credentialless` is what lets them load at all.
|
|
190
|
+
// `attr:` + `true | undefined` — see the note in `IframeRenderer`.
|
|
191
|
+
attr:credentialless={
|
|
192
|
+
// No sandbox here: a YouTube/Vimeo embed keeps its provider
|
|
193
|
+
// cookies, so `'auto'` leaves it alone and a COEP host opts in
|
|
194
|
+
// with `iframeCredentialless: 'always'`.
|
|
195
|
+
shouldSetCredentialless(embedUrl()!, config, {
|
|
196
|
+
sandboxedWithoutSameOrigin: false,
|
|
197
|
+
}) || undefined
|
|
198
|
+
}
|
|
184
199
|
loading="lazy"
|
|
185
200
|
/>
|
|
186
201
|
</Show>
|
|
187
202
|
</div>
|
|
188
203
|
|
|
204
|
+
{/* v6.21.0 — a COEP-blocked embed is silent; this link is the way out. */}
|
|
205
|
+
<Show when={embedUrl()}>
|
|
206
|
+
<IframeFallbackLink url={params()?.url || ''} />
|
|
207
|
+
</Show>
|
|
208
|
+
|
|
189
209
|
{/* Caption */}
|
|
190
210
|
<Show when={params()?.caption}>
|
|
191
211
|
<div class="px-4 py-3 border-t border-gray-200 dark:border-gray-700 flex-shrink-0">
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCPUIConfigContext — host-level rendering policy for the library's iframes.
|
|
3
|
+
*
|
|
4
|
+
* @since v6.21.0
|
|
5
|
+
*
|
|
6
|
+
* ## Why
|
|
7
|
+
*
|
|
8
|
+
* A host that serves `Cross-Origin-Embedder-Policy: credentialless` makes the
|
|
9
|
+
* browser refuse every cross-origin iframe whose own document does not send a
|
|
10
|
+
* COEP header — which is YouTube, Vimeo, Google Docs, Notion and nearly all of
|
|
11
|
+
* `DEFAULT_IFRAME_DOMAINS`. The escape hatch is the boolean HTML attribute
|
|
12
|
+
* `credentialless` on the `<iframe>` itself: the document then loads, but
|
|
13
|
+
* without access to the cookies and storage of its own origin.
|
|
14
|
+
*
|
|
15
|
+
* That trade-off is free for a public video or chart and fatal for an
|
|
16
|
+
* authenticated embed, so the library keys it on the distinction it already
|
|
17
|
+
* makes: `TRUSTED_IFRAME_DOMAINS` (the hosts that receive `allow-same-origin`
|
|
18
|
+
* because they need their own cookies) do NOT get the attribute; everything
|
|
19
|
+
* else does. This context lets a host override that, add its own trusted
|
|
20
|
+
* hosts, and control the "open in a new tab" fallback link.
|
|
21
|
+
*
|
|
22
|
+
* ## Scope — policy only, NOT strings
|
|
23
|
+
*
|
|
24
|
+
* Text lives in `MCPUIStrings` (`MCPUIStringsContext`). This context carries
|
|
25
|
+
* only behavioural switches, and like the strings context every renderer works
|
|
26
|
+
* standalone: with no provider mounted, `DEFAULT_MCPUI_CONFIG` applies.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* import { MCPUIConfigProvider } from '@seed-ship/mcp-ui-solid'
|
|
31
|
+
*
|
|
32
|
+
* <MCPUIConfigProvider config={{ customTrustedIframeDomains: ['embed.acme.com'] }}>
|
|
33
|
+
* <App />
|
|
34
|
+
* </MCPUIConfigProvider>
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { createContext, mergeProps, useContext, type JSX } from 'solid-js'
|
|
39
|
+
import type { IframePolicy } from '../types'
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Host-level rendering policy. Every field has a default — a provider passes
|
|
43
|
+
* a partial override.
|
|
44
|
+
*/
|
|
45
|
+
export interface MCPUIConfig {
|
|
46
|
+
/**
|
|
47
|
+
* When the library puts the boolean `credentialless` attribute on an
|
|
48
|
+
* `<iframe>` it renders.
|
|
49
|
+
*
|
|
50
|
+
* - `'auto'` (default) — on every iframe whose host is NOT in
|
|
51
|
+
* `TRUSTED_IFRAME_DOMAINS` (plus `customTrustedIframeDomains`). Those
|
|
52
|
+
* hosts already ran without cookies, since their sandbox has no
|
|
53
|
+
* `allow-same-origin`; the attribute is what unblocks them under COEP.
|
|
54
|
+
* - `'always'` — on every iframe, trusted hosts included. An authenticated
|
|
55
|
+
* embed will then render its login screen.
|
|
56
|
+
* - `'never'` — never. The right value for a host that sends no COEP header
|
|
57
|
+
* and does not want its embeds to lose their cookies.
|
|
58
|
+
*
|
|
59
|
+
* Chrome/Edge 110+ honour the attribute; Firefox ignores it (iframes stay
|
|
60
|
+
* blocked while the host sends COEP); Safari ignores the COEP value itself,
|
|
61
|
+
* so nothing is blocked there in the first place.
|
|
62
|
+
*/
|
|
63
|
+
iframeCredentialless: 'auto' | 'always' | 'never'
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Extra hosts treated as trusted, on top of `TRUSTED_IFRAME_DOMAINS`:
|
|
67
|
+
* no `credentialless` attribute, and `allow-same-origin` in the sandbox.
|
|
68
|
+
* A subdomain of a listed host matches too.
|
|
69
|
+
*
|
|
70
|
+
* This only RECLASSIFIES a host the allow-list already accepts. To render an
|
|
71
|
+
* iframe whose host is outside `DEFAULT_IFRAME_DOMAINS`, add it to
|
|
72
|
+
* {@link MCPUIConfig.customIframeDomains} with `iframePolicy: 'extend'`.
|
|
73
|
+
*/
|
|
74
|
+
customTrustedIframeDomains: string[]
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* How `UIResourceRenderer` validates an `iframe` component's host:
|
|
78
|
+
* `'strict'` (default) accepts `DEFAULT_IFRAME_DOMAINS` only, `'extend'`
|
|
79
|
+
* also accepts {@link MCPUIConfig.customIframeDomains}.
|
|
80
|
+
*/
|
|
81
|
+
iframePolicy: IframePolicy
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Extra hosts the allow-list accepts when `iframePolicy` is `'extend'`.
|
|
85
|
+
* Without this, a component pointing at an unlisted host is replaced by the
|
|
86
|
+
* validation card before any renderer runs.
|
|
87
|
+
*/
|
|
88
|
+
customIframeDomains: string[]
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* When the library renders an "open in a new tab" link under an embed
|
|
92
|
+
* (`strings.iframeOpenInNewTab`).
|
|
93
|
+
*
|
|
94
|
+
* A COEP-blocked iframe fires no event the parent page can observe, so
|
|
95
|
+
* without this the user sees an unexplained blank frame.
|
|
96
|
+
*
|
|
97
|
+
* - `'auto'` (default) — only when the page is cross-origin isolated
|
|
98
|
+
* (`window.crossOriginIsolated`). That flag needs COOP `same-origin` AND
|
|
99
|
+
* COEP, so a host that sends COEP alone still blocks embeds while the
|
|
100
|
+
* flag is false: set `'always'` there. Read after mount, never during
|
|
101
|
+
* render, so SSR markup and hydration agree.
|
|
102
|
+
* - `'always'` / `'never'` — unconditional.
|
|
103
|
+
*/
|
|
104
|
+
iframeFallbackLink: 'auto' | 'always' | 'never'
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Defaults. Chosen so a host that sends no COEP header sees no change from
|
|
109
|
+
* 6.20.0 apart from the fallback link, which `'auto'` keeps hidden there.
|
|
110
|
+
*/
|
|
111
|
+
export const DEFAULT_MCPUI_CONFIG: MCPUIConfig = {
|
|
112
|
+
iframeCredentialless: 'auto',
|
|
113
|
+
customTrustedIframeDomains: [],
|
|
114
|
+
iframePolicy: 'strict',
|
|
115
|
+
customIframeDomains: [],
|
|
116
|
+
iframeFallbackLink: 'auto',
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const MCPUIConfigContext = createContext<MCPUIConfig>(DEFAULT_MCPUI_CONFIG)
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Reads the active host config. Returns `DEFAULT_MCPUI_CONFIG` when no
|
|
123
|
+
* `<MCPUIConfigProvider>` is mounted above.
|
|
124
|
+
*/
|
|
125
|
+
export function useMCPUIConfig(): MCPUIConfig {
|
|
126
|
+
return mergeProps(DEFAULT_MCPUI_CONFIG, useContext(MCPUIConfigContext))
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface MCPUIConfigProviderProps {
|
|
130
|
+
/**
|
|
131
|
+
* Partial override of the host config. Any key left unset falls back to
|
|
132
|
+
* `DEFAULT_MCPUI_CONFIG`.
|
|
133
|
+
*/
|
|
134
|
+
config?: Partial<MCPUIConfig>
|
|
135
|
+
children: JSX.Element
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Provides the host config to every MCP-UI renderer below it.
|
|
140
|
+
* Merges the partial `config` override over the defaults.
|
|
141
|
+
*/
|
|
142
|
+
export function MCPUIConfigProvider(props: MCPUIConfigProviderProps): JSX.Element {
|
|
143
|
+
// The function source keeps replacement `config` objects reactive while
|
|
144
|
+
// mergeProps fills any omitted key without taking a one-time snapshot.
|
|
145
|
+
const value = mergeProps(DEFAULT_MCPUI_CONFIG, () => props.config)
|
|
146
|
+
return (
|
|
147
|
+
<MCPUIConfigContext.Provider value={value}>
|
|
148
|
+
{props.children}
|
|
149
|
+
</MCPUIConfigContext.Provider>
|
|
150
|
+
)
|
|
151
|
+
}
|
|
@@ -323,6 +323,13 @@ export interface MCPUIStrings {
|
|
|
323
323
|
imageViewFullSize?: string
|
|
324
324
|
/** Fallback `title` of an iframe without one. */
|
|
325
325
|
iframeTitle?: string
|
|
326
|
+
/**
|
|
327
|
+
* Label of the "open in a new tab" link rendered under an embed
|
|
328
|
+
* (`IframeRenderer`, `VideoRenderer`). A COEP-blocked iframe fires no
|
|
329
|
+
* observable event, so this link is the only way out of a blank frame.
|
|
330
|
+
* @since 6.21.0
|
|
331
|
+
*/
|
|
332
|
+
iframeOpenInNewTab?: string
|
|
326
333
|
/** Fallback label of a link component without one. */
|
|
327
334
|
linkLabel?: string
|
|
328
335
|
/**
|
|
@@ -1085,6 +1092,9 @@ export const DEFAULT_MCPUI_STRINGS: Required<MCPUIStrings> = {
|
|
|
1085
1092
|
sizeBytes: '{size} B',
|
|
1086
1093
|
sizeKilobytes: '{size} KB',
|
|
1087
1094
|
sizeMegabytes: '{size} MB',
|
|
1095
|
+
|
|
1096
|
+
// ── 6.21.0 — iframe COEP fallback ──────────────────────────
|
|
1097
|
+
iframeOpenInNewTab: 'Open in a new tab',
|
|
1088
1098
|
}
|
|
1089
1099
|
|
|
1090
1100
|
/**
|
package/src/index.ts
CHANGED
|
@@ -142,6 +142,18 @@ export type {
|
|
|
142
142
|
MCPUIStringsProviderProps,
|
|
143
143
|
} from './context/MCPUIStringsContext'
|
|
144
144
|
|
|
145
|
+
// Host rendering policy — iframe `credentialless` / COEP (v6.21.0)
|
|
146
|
+
export {
|
|
147
|
+
MCPUIConfigProvider,
|
|
148
|
+
MCPUIConfigContext,
|
|
149
|
+
useMCPUIConfig,
|
|
150
|
+
DEFAULT_MCPUI_CONFIG,
|
|
151
|
+
} from './context/MCPUIConfigContext'
|
|
152
|
+
export type {
|
|
153
|
+
MCPUIConfig,
|
|
154
|
+
MCPUIConfigProviderProps,
|
|
155
|
+
} from './context/MCPUIConfigContext'
|
|
156
|
+
|
|
145
157
|
// Telemetry sink (B.5 — v5.6.0)
|
|
146
158
|
export {
|
|
147
159
|
MCPUITelemetryProvider,
|
|
@@ -331,6 +343,9 @@ export {
|
|
|
331
343
|
validateLayout,
|
|
332
344
|
validateIframeDomain,
|
|
333
345
|
getIframeSandbox,
|
|
346
|
+
// v6.21.0 — the trusted-host test behind `getIframeSandbox` and the
|
|
347
|
+
// `credentialless` iframe attribute.
|
|
348
|
+
isTrustedIframeDomain,
|
|
334
349
|
DEFAULT_RESOURCE_LIMITS,
|
|
335
350
|
DEFAULT_IFRAME_DOMAINS,
|
|
336
351
|
TRUSTED_IFRAME_DOMAINS,
|