-
Notifications
You must be signed in to change notification settings - Fork 250
feat: add skybridge integration #140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
a4136af
dbe9324
8d736a7
30a7ddb
cb2d62a
385b662
7fd79b9
514aca1
929f121
6adfb39
9e3e93f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,6 +49,7 @@ web_modules/ | |
|
|
||
| # PNPM | ||
| .pnpm-store/ | ||
| .pnpm-home/ | ||
|
|
||
| # TypeScript cache | ||
| *.tsbuildinfo | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| packages: | ||
| # all packages in subdirs of sdks/typescript/ | ||
| - 'sdks/typescript/*' | ||
| - 'examples/*' | ||
| - 'docs' | ||
| - sdks/typescript/* | ||
| - examples/* | ||
| - docs | ||
| onlyBuiltDependencies: | ||
| - esbuild |
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @idosal you recently added the double proxy. what client was that for? I don't think this is a breaking change since it is the code clients need to add to their CDN anyway but curious if you tested the previous implementation
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not used in production yet, so we can change it. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -47,27 +47,42 @@ | |
| if (contentType === 'rawhtml') { | ||
| // Double-iframe raw HTML mode (HTML sent via postMessage) | ||
| const inner = document.createElement('iframe'); | ||
| let pendingHtml = null; | ||
| const renderHtmlInIframe = (markup) => { | ||
| const doc = inner.contentDocument || inner.contentWindow?.document; | ||
| if (!doc) return false; | ||
| try { | ||
| doc.open(); | ||
| doc.write(markup); | ||
| doc.close(); | ||
| return true; | ||
| } catch (error) { | ||
| return false; | ||
| } | ||
| }; | ||
| inner.addEventListener('load', () => { | ||
| if (pendingHtml !== null && renderHtmlInIframe(pendingHtml)) { | ||
| pendingHtml = null; | ||
| } | ||
| }); | ||
| inner.style = 'width:100%; height:100%; border:none;'; | ||
| // sandbox will be set from postMessage payload; default minimal before html arrives | ||
| inner.setAttribute('sandbox', 'allow-scripts'); | ||
| // Default sandbox ensures same-origin access for DOM writing; parent may override | ||
| inner.setAttribute('sandbox', 'allow-scripts allow-same-origin'); | ||
| inner.src = 'about:blank'; | ||
| document.body.appendChild(inner); | ||
|
|
||
| // Wait for HTML content from parent | ||
| window.addEventListener('message', (event) => { | ||
| if (event.source === window.parent) { | ||
| if (event.data && event.data.type === 'ui-html-content') { | ||
| const payload = event.data.payload || {}; | ||
| const html = payload.html; | ||
| const sandbox = payload.sandbox; | ||
| if (typeof sandbox === 'string') { | ||
| inner.setAttribute('sandbox', sandbox); | ||
| } | ||
| if (typeof html === 'string') { | ||
| inner.srcdoc = html; | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the key was to use srcdoc changes the root url and breaks client side navigation.
and document.write() do the trick
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great catch! |
||
| } | ||
| } else { | ||
| if (inner && inner.contentWindow) { | ||
| inner.contentWindow.postMessage(event.data, '*'); | ||
| if (event.source === window.parent && event.data && event.data.type === 'ui-html-content') { | ||
| const payload = event.data.payload || {}; | ||
| const html = payload.html; | ||
| const sandbox = payload.sandbox; | ||
| if (typeof sandbox === 'string') { | ||
| inner.setAttribute('sandbox', sandbox); | ||
| } | ||
| if (typeof html === 'string') { | ||
| if (!renderHtmlInIframe(html)) { | ||
| pendingHtml = html; | ||
| } | ||
| } | ||
| } else if (event.source === inner.contentWindow) { | ||
|
|
||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,9 @@ export type HTMLResourceRendererProps = { | |
| style?: React.CSSProperties; | ||
| proxy?: string; | ||
| iframeRenderData?: Record<string, unknown>; | ||
| toolInput?: Record<string, unknown>; | ||
| toolName?: string; | ||
| toolResponseMetadata?: Record<string, unknown>; | ||
|
||
| autoResizeIframe?: boolean | { width?: boolean; height?: boolean }; | ||
| sandboxPermissions?: string; | ||
| iframeProps?: Omit<React.HTMLAttributes<HTMLIFrameElement>, 'src' | 'srcDoc' | 'style'> & { | ||
|
|
@@ -43,18 +46,16 @@ export const HTMLResourceRenderer = ({ | |
| style, | ||
| proxy, | ||
| iframeRenderData, | ||
| toolInput, | ||
| toolName, | ||
| toolResponseMetadata, | ||
| autoResizeIframe, | ||
| sandboxPermissions, | ||
| iframeProps, | ||
| }: HTMLResourceRendererProps) => { | ||
| const iframeRef = useRef<HTMLIFrameElement | null>(null); | ||
| useImperativeHandle(iframeProps?.ref, () => iframeRef.current as HTMLIFrameElement); | ||
|
|
||
| const { error, iframeSrc, iframeRenderMode, htmlString } = useMemo( | ||
| () => processHTMLResource(resource, proxy), | ||
| [resource, proxy], | ||
| ); | ||
|
|
||
| const uiMetadata = useMemo(() => getUIResourceMetadata(resource), [resource]); | ||
| const preferredFrameSize = uiMetadata[UIMetadataKey.PREFERRED_FRAME_SIZE] ?? ['100%', '100%']; | ||
| const metadataInitialRenderData = uiMetadata[UIMetadataKey.INITIAL_RENDER_DATA] ?? undefined; | ||
|
|
@@ -69,6 +70,20 @@ export const HTMLResourceRenderer = ({ | |
| }; | ||
| }, [iframeRenderData, metadataInitialRenderData]); | ||
|
|
||
| const { error, iframeSrc, iframeRenderMode, htmlString } = useMemo( | ||
| () => | ||
| processHTMLResource( | ||
| resource, | ||
| proxy, | ||
| initialRenderData, | ||
| toolInput, | ||
| toolName, | ||
| toolResponseMetadata | ||
| ), | ||
| [resource, proxy] | ||
| ); | ||
|
|
||
|
|
||
| const iframeSrcToRender = useMemo(() => { | ||
| if (iframeSrc && initialRenderData) { | ||
| const iframeUrl = new URL(iframeSrc); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,12 @@ import React from 'react'; | |
|
|
||
| export type UIActionType = 'tool' | 'prompt' | 'link' | 'intent' | 'notify'; | ||
|
|
||
| export const ALL_RESOURCE_CONTENT_TYPES = ['rawHtml', 'externalUrl', 'remoteDom'] as const; | ||
| export const ALL_RESOURCE_CONTENT_TYPES = [ | ||
| 'rawHtml', | ||
| 'externalUrl', | ||
| 'remoteDom', | ||
| 'skybridge', | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| ] as const; | ||
| export type ResourceContentType = (typeof ALL_RESOURCE_CONTENT_TYPES)[number]; | ||
|
|
||
| type GenericActionMessage = { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@idosal @liady this is the main change to support the double iframe exactly the same way as chatGTP.
also I think line 6 the meta CSP is not required, I had to remove it since the CSP can be set on the actual remote html and needs to be super relaxed otherwise nothing load