@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.
Files changed (69) hide show
  1. package/CHANGELOG.md +144 -0
  2. package/README.md +120 -3
  3. package/dist/components/IframeFallbackLink.cjs +37 -0
  4. package/dist/components/IframeFallbackLink.cjs.map +1 -0
  5. package/dist/components/IframeFallbackLink.d.ts +23 -0
  6. package/dist/components/IframeFallbackLink.d.ts.map +1 -0
  7. package/dist/components/IframeFallbackLink.js +37 -0
  8. package/dist/components/IframeFallbackLink.js.map +1 -0
  9. package/dist/components/UIResourceRenderer.cjs +150 -127
  10. package/dist/components/UIResourceRenderer.cjs.map +1 -1
  11. package/dist/components/UIResourceRenderer.d.ts.map +1 -1
  12. package/dist/components/UIResourceRenderer.js +151 -128
  13. package/dist/components/UIResourceRenderer.js.map +1 -1
  14. package/dist/components/VideoRenderer.cjs +45 -19
  15. package/dist/components/VideoRenderer.cjs.map +1 -1
  16. package/dist/components/VideoRenderer.d.ts.map +1 -1
  17. package/dist/components/VideoRenderer.js +45 -19
  18. package/dist/components/VideoRenderer.js.map +1 -1
  19. package/dist/context/MCPUIConfigContext.cjs +29 -0
  20. package/dist/context/MCPUIConfigContext.cjs.map +1 -0
  21. package/dist/context/MCPUIConfigContext.d.ts +124 -0
  22. package/dist/context/MCPUIConfigContext.d.ts.map +1 -0
  23. package/dist/context/MCPUIConfigContext.js +29 -0
  24. package/dist/context/MCPUIConfigContext.js.map +1 -0
  25. package/dist/context/MCPUIStringsContext.cjs +3 -1
  26. package/dist/context/MCPUIStringsContext.cjs.map +1 -1
  27. package/dist/context/MCPUIStringsContext.d.ts +7 -0
  28. package/dist/context/MCPUIStringsContext.d.ts.map +1 -1
  29. package/dist/context/MCPUIStringsContext.js +3 -1
  30. package/dist/context/MCPUIStringsContext.js.map +1 -1
  31. package/dist/index.cjs +6 -0
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +3 -1
  34. package/dist/index.d.ts +3 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +7 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/services/index.d.ts +1 -1
  39. package/dist/services/index.d.ts.map +1 -1
  40. package/dist/services/validation.cjs +11 -10
  41. package/dist/services/validation.cjs.map +1 -1
  42. package/dist/services/validation.d.ts +20 -0
  43. package/dist/services/validation.d.ts.map +1 -1
  44. package/dist/services/validation.js +11 -10
  45. package/dist/services/validation.js.map +1 -1
  46. package/dist/utils/iframe-coep.cjs +30 -0
  47. package/dist/utils/iframe-coep.cjs.map +1 -0
  48. package/dist/utils/iframe-coep.d.ts +46 -0
  49. package/dist/utils/iframe-coep.d.ts.map +1 -0
  50. package/dist/utils/iframe-coep.js +30 -0
  51. package/dist/utils/iframe-coep.js.map +1 -0
  52. package/dist/validation.cjs +2 -0
  53. package/dist/validation.cjs.map +1 -1
  54. package/dist/validation.d.cts +1 -1
  55. package/dist/validation.d.ts +1 -1
  56. package/dist/validation.d.ts.map +1 -1
  57. package/dist/validation.js +3 -1
  58. package/package.json +1 -1
  59. package/src/components/IframeFallbackLink.tsx +57 -0
  60. package/src/components/UIResourceRenderer.tsx +31 -4
  61. package/src/components/VideoRenderer.tsx +20 -0
  62. package/src/context/MCPUIConfigContext.tsx +151 -0
  63. package/src/context/MCPUIStringsContext.tsx +10 -0
  64. package/src/index.ts +15 -0
  65. package/src/services/index.ts +1 -0
  66. package/src/services/validation.ts +38 -16
  67. package/src/solid-jsx-attrs.d.ts +24 -0
  68. package/src/utils/iframe-coep.ts +74 -0
  69. 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
- try {
711
- const domain = new URL(url).hostname;
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.