@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
|
@@ -689,6 +689,42 @@ export function validateIframeDomain(
|
|
|
689
689
|
}
|
|
690
690
|
}
|
|
691
691
|
|
|
692
|
+
/**
|
|
693
|
+
* Is this URL's host a trusted iframe host?
|
|
694
|
+
*
|
|
695
|
+
* A host matches when it equals an entry of `TRUSTED_IFRAME_DOMAINS` (plus
|
|
696
|
+
* `options.customTrustedDomains`) or is a subdomain of one. Trusted hosts
|
|
697
|
+
* need their own cookies/storage to authenticate, which is why they receive
|
|
698
|
+
* `allow-same-origin` in `getIframeSandbox` and deliberately do NOT receive
|
|
699
|
+
* the `credentialless` iframe attribute (an authenticated embed loaded
|
|
700
|
+
* without its cookies only shows a login screen).
|
|
701
|
+
*
|
|
702
|
+
* An unparsable URL is never trusted.
|
|
703
|
+
*
|
|
704
|
+
* @param url - The iframe URL
|
|
705
|
+
* @param options - Optional custom trusted domains, merged over the defaults
|
|
706
|
+
* @returns `true` when the URL's host is trusted
|
|
707
|
+
* @since v6.21.0 — extracted from `getIframeSandbox`, identical matching.
|
|
708
|
+
*/
|
|
709
|
+
export function isTrustedIframeDomain(
|
|
710
|
+
url: string,
|
|
711
|
+
options?: { customTrustedDomains?: string[] }
|
|
712
|
+
): boolean {
|
|
713
|
+
try {
|
|
714
|
+
const domain = new URL(url).hostname;
|
|
715
|
+
const trustedList = options?.customTrustedDomains
|
|
716
|
+
? [...TRUSTED_IFRAME_DOMAINS, ...options.customTrustedDomains]
|
|
717
|
+
: TRUSTED_IFRAME_DOMAINS;
|
|
718
|
+
|
|
719
|
+
return trustedList.some(
|
|
720
|
+
(trusted) => domain === trusted || domain.endsWith(`.${trusted}`)
|
|
721
|
+
);
|
|
722
|
+
} catch {
|
|
723
|
+
// Invalid URL — never trusted.
|
|
724
|
+
return false;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
692
728
|
/**
|
|
693
729
|
* Get the appropriate sandbox attribute for an iframe URL.
|
|
694
730
|
*
|
|
@@ -707,22 +743,8 @@ export function getIframeSandbox(
|
|
|
707
743
|
): string {
|
|
708
744
|
const baseSandbox = 'allow-scripts allow-popups';
|
|
709
745
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
let trustedList = TRUSTED_IFRAME_DOMAINS;
|
|
713
|
-
if (options?.customTrustedDomains) {
|
|
714
|
-
trustedList = [...TRUSTED_IFRAME_DOMAINS, ...options.customTrustedDomains];
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
const isTrusted = trustedList.some(
|
|
718
|
-
(trusted) => domain === trusted || domain.endsWith(`.${trusted}`)
|
|
719
|
-
);
|
|
720
|
-
|
|
721
|
-
if (isTrusted) {
|
|
722
|
-
return `${baseSandbox} allow-same-origin allow-forms`;
|
|
723
|
-
}
|
|
724
|
-
} catch {
|
|
725
|
-
// Invalid URL — use restrictive sandbox
|
|
746
|
+
if (isTrustedIframeDomain(url, options)) {
|
|
747
|
+
return `${baseSandbox} allow-same-origin allow-forms`;
|
|
726
748
|
}
|
|
727
749
|
|
|
728
750
|
return baseSandbox;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solid JSX augmentation — `attr:credentialless` (v6.21.0).
|
|
3
|
+
*
|
|
4
|
+
* `credentialless` is listed in dom-expressions' `Properties` set, so the
|
|
5
|
+
* plain `credentialless={…}` form compiles to a JS property assignment
|
|
6
|
+
* (`el.credentialless = v`) and never reaches the DOM as an attribute in an
|
|
7
|
+
* engine that does not reflect it (jsdom, and any pre-110 browser). The
|
|
8
|
+
* `attr:` namespace forces `setAttribute` / `ssrAttribute` instead.
|
|
9
|
+
*
|
|
10
|
+
* Solid types `attr:*` only for keys declared in `JSX.ExplicitAttributes`,
|
|
11
|
+
* which ships empty — hence this augmentation. The value type is
|
|
12
|
+
* `true | undefined` on purpose: `undefined` removes the attribute, so the
|
|
13
|
+
* boolean attribute is never emitted as `credentialless="false"`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import 'solid-js'
|
|
17
|
+
|
|
18
|
+
declare module 'solid-js' {
|
|
19
|
+
namespace JSX {
|
|
20
|
+
interface ExplicitAttributes {
|
|
21
|
+
credentialless: true | undefined
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
|
|
15
|
+
import { createSignal, onMount, type Accessor } from 'solid-js'
|
|
16
|
+
import { isTrustedIframeDomain } from '../services/validation'
|
|
17
|
+
import type { MCPUIConfig } from '../context/MCPUIConfigContext'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Does this iframe get the boolean `credentialless` attribute?
|
|
21
|
+
*
|
|
22
|
+
* `'auto'` (the default) says yes only when the embed has nothing to lose:
|
|
23
|
+
* the host is not trusted AND the iframe already runs without cookies because
|
|
24
|
+
* its `sandbox` has no `allow-same-origin` (`sandboxedWithoutSameOrigin`).
|
|
25
|
+
* The video embed carries no sandbox, so in `'auto'` it keeps its provider
|
|
26
|
+
* cookies and a COEP host must opt in with `'always'`.
|
|
27
|
+
*
|
|
28
|
+
* An unparsable URL is not trusted, so it gets the attribute.
|
|
29
|
+
*/
|
|
30
|
+
export function shouldSetCredentialless(
|
|
31
|
+
url: string,
|
|
32
|
+
config: MCPUIConfig,
|
|
33
|
+
options: { sandboxedWithoutSameOrigin: boolean }
|
|
34
|
+
): boolean {
|
|
35
|
+
const mode = config.iframeCredentialless
|
|
36
|
+
if (mode === 'always') return true
|
|
37
|
+
if (mode === 'never') return false
|
|
38
|
+
if (!options.sandboxedWithoutSameOrigin) return false
|
|
39
|
+
return !isTrustedIframeDomain(url, {
|
|
40
|
+
customTrustedDomains: config.customTrustedIframeDomains,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* `window.crossOriginIsolated`, read **after mount** into a signal.
|
|
46
|
+
*
|
|
47
|
+
* Reading it during render would make the server emit markup the client
|
|
48
|
+
* cannot reproduce; the signal starts at `false` (the SSR value) and flips
|
|
49
|
+
* once mounted, so hydration always matches.
|
|
50
|
+
*/
|
|
51
|
+
export function createCrossOriginIsolated(): Accessor<boolean> {
|
|
52
|
+
const [isolated, setIsolated] = createSignal(false)
|
|
53
|
+
onMount(() => {
|
|
54
|
+
setIsolated(typeof window !== 'undefined' && window.crossOriginIsolated === true)
|
|
55
|
+
})
|
|
56
|
+
return isolated
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Does the "open in a new tab" link show under an embed?
|
|
61
|
+
*
|
|
62
|
+
* `'auto'` shows it only on a cross-origin-isolated page — exactly where COEP
|
|
63
|
+
* can block the frame, and where the blocking is silent (a refused
|
|
64
|
+
* cross-origin document fires no `error` event the parent can observe).
|
|
65
|
+
*/
|
|
66
|
+
export function shouldShowIframeFallbackLink(
|
|
67
|
+
config: MCPUIConfig,
|
|
68
|
+
crossOriginIsolated: boolean
|
|
69
|
+
): boolean {
|
|
70
|
+
const mode = config.iframeFallbackLink
|
|
71
|
+
if (mode === 'always') return true
|
|
72
|
+
if (mode === 'never') return false
|
|
73
|
+
return crossOriginIsolated
|
|
74
|
+
}
|
package/src/validation.ts
CHANGED
|
@@ -19,6 +19,10 @@ export {
|
|
|
19
19
|
validateTableComponent,
|
|
20
20
|
validatePayloadSize,
|
|
21
21
|
validateIframeDomain,
|
|
22
|
+
getIframeSandbox,
|
|
23
|
+
// v6.21.0 — trusted-host test shared by the sandbox and the
|
|
24
|
+
// `credentialless` iframe attribute.
|
|
25
|
+
isTrustedIframeDomain,
|
|
22
26
|
sanitizeString,
|
|
23
27
|
DEFAULT_RESOURCE_LIMITS,
|
|
24
28
|
// v6.20.0 — end-user form validation with injectable wording.
|