mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-16 15:45:13 +00:00
Fix mermaid / contentkit webframe not updating properly (#2427)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@gitbook/react-contentkit': patch
|
||||
---
|
||||
|
||||
Fix rendering of webframe with SSR causing wrong communication between frame and renderer
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'gitbook': patch
|
||||
---
|
||||
|
||||
Add optional env `GITBOOK_INTEGRATIONS_HOST` to configure the host serving the integrations
|
||||
@@ -3,6 +3,7 @@ import { Icon } from '@gitbook/icons';
|
||||
import { ContentKit, ContentKitOutput, ContentKitServerContext } from '@gitbook/react-contentkit';
|
||||
|
||||
import { ignoreAPIError, renderIntegrationUi } from '@/lib/api';
|
||||
import { INTEGRATIONS_HOST } from '@/lib/csp';
|
||||
import { parseMarkdown } from '@/lib/markdown';
|
||||
import { tcls } from '@/lib/tailwind';
|
||||
|
||||
@@ -69,7 +70,7 @@ export async function IntegrationBlock(props: BlockProps<DocumentBlockIntegratio
|
||||
return (
|
||||
<div className={tcls(style)}>
|
||||
<ContentKit
|
||||
security={{ firstPartyDomains: ['integrations.gitbook.com'] }}
|
||||
security={{ firstPartyDomains: [INTEGRATIONS_HOST] }}
|
||||
initialInput={initialInput}
|
||||
initialOutput={initialOutput}
|
||||
render={async (request) => {
|
||||
|
||||
@@ -25,6 +25,12 @@ export function createContentSecurityPolicyNonce(): string {
|
||||
return nonce;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hostname serving the integrations.
|
||||
*/
|
||||
export const INTEGRATIONS_HOST =
|
||||
process.env.GITBOOK_INTEGRATIONS_HOST ?? 'integrations.gitbook.com';
|
||||
|
||||
/**
|
||||
* Generate a Content Security Policy header for a space.
|
||||
*/
|
||||
@@ -39,10 +45,10 @@ export function getContentSecurityPolicy(scripts: SpaceIntegrationScript[], nonc
|
||||
// Since I can't get the nonce to work for inline styles, we need to allow unsafe-inline
|
||||
const defaultCSP = `
|
||||
default-src 'self' ${assetsDomain};
|
||||
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' 'unsafe-inline' 'unsafe-eval' ${assetsDomain} https://integrations.gitbook.com https://cdn.iframe.ly;
|
||||
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' 'unsafe-inline' 'unsafe-eval' ${assetsDomain} https://${INTEGRATIONS_HOST} https://cdn.iframe.ly;
|
||||
style-src 'self' ${assetsDomain} fonts.googleapis.com 'unsafe-inline';
|
||||
img-src * 'self' blob: data: files.gitbook.com ${assetsDomain} ${iconsAssetsSrc};
|
||||
connect-src * 'self' integrations.gitbook.com app.gitbook.com api.gitbook.com srv.buysellads.com ${assetsDomain} ${iconsAssetsSrc};
|
||||
connect-src * 'self' ${INTEGRATIONS_HOST} app.gitbook.com api.gitbook.com srv.buysellads.com ${assetsDomain} ${iconsAssetsSrc};
|
||||
font-src 'self' fonts.gstatic.com ${assetsDomain};
|
||||
frame-src *;
|
||||
object-src 'none';
|
||||
|
||||
@@ -10,6 +10,7 @@ import { resolveDynamicBinding } from './dynamic';
|
||||
export function ElementWebframe(props: ContentKitClientElementProps<ContentKitWebFrame>) {
|
||||
const { element } = props;
|
||||
|
||||
const [mounted, setMounted] = React.useState(false);
|
||||
const renderer = useContentKitClientContext();
|
||||
const iframeRef = React.useRef<HTMLIFrameElement>(null);
|
||||
const [size, setSize] = React.useState<{
|
||||
@@ -23,10 +24,6 @@ export function ElementWebframe(props: ContentKitClientElementProps<ContentKitWe
|
||||
|
||||
const sendMessage = React.useCallback(
|
||||
(message: object) => {
|
||||
if (!iframeRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = new URL(element.source.url);
|
||||
|
||||
// For security reasons, only iframe from our integrations domains are allowed
|
||||
@@ -36,6 +33,10 @@ export function ElementWebframe(props: ContentKitClientElementProps<ContentKitWe
|
||||
}
|
||||
|
||||
if (readyRef.current) {
|
||||
if (!iframeRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
iframeRef.current.contentWindow!.postMessage(
|
||||
message,
|
||||
`${target.protocol}//${target.host}`,
|
||||
@@ -61,7 +62,7 @@ export function ElementWebframe(props: ContentKitClientElementProps<ContentKitWe
|
||||
|
||||
// For security reasons, only iframe from our integrations domains are allowed
|
||||
// to send and receive messages
|
||||
if (!renderer.security.firstPartyDomains.includes(origin.host) && 0) {
|
||||
if (!renderer.security.firstPartyDomains.includes(origin.host)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -117,6 +118,11 @@ export function ElementWebframe(props: ContentKitClientElementProps<ContentKitWe
|
||||
};
|
||||
|
||||
window.addEventListener('message', callback);
|
||||
|
||||
// We only render the iframe once we have added the event listener
|
||||
// otherwise during SSR, we'll miss messages
|
||||
setMounted(true);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('message', callback);
|
||||
};
|
||||
@@ -142,26 +148,29 @@ export function ElementWebframe(props: ContentKitClientElementProps<ContentKitWe
|
||||
<div
|
||||
className={`contentkit-webframe`}
|
||||
style={{
|
||||
aspectRatio: element.aspectRatio,
|
||||
...size,
|
||||
aspectRatio: size.aspectRatio || element.aspectRatio || undefined,
|
||||
maxWidth: size.maxWidth || undefined,
|
||||
maxHeight: size.maxHeight || undefined,
|
||||
}}
|
||||
>
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
src={element.source.url}
|
||||
allowFullScreen
|
||||
allow="clipboard-write"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
border: 'none',
|
||||
}}
|
||||
/>
|
||||
{mounted ? (
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
src={element.source.url}
|
||||
allowFullScreen
|
||||
allow="clipboard-write"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
border: 'none',
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user