diff --git a/app/application.ts b/app/application.ts index ba0d8f877..38dc08993 100644 --- a/app/application.ts +++ b/app/application.ts @@ -1,5 +1,8 @@ import loadPolyfills from './soapbox/load_polyfills'; +// Load iframe event listener +require('./soapbox/iframe'); + // @ts-ignore require.context('./images/', true); diff --git a/app/soapbox/components/safe-embed.tsx b/app/soapbox/components/safe-embed.tsx new file mode 100644 index 000000000..b794bbed2 --- /dev/null +++ b/app/soapbox/components/safe-embed.tsx @@ -0,0 +1,63 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; + +interface ISafeEmbed { + /** Styles for the outer frame element. */ + className?: string, + /** Space-separate list of restrictions to ALLOW for the iframe. */ + sandbox?: string, + /** Unique title for the iframe. */ + title: string, + /** HTML body to embed. */ + html?: string, +} + +/** Safely embeds arbitrary HTML content on the page (by putting it in an iframe). */ +const SafeEmbed: React.FC = ({ + className, + sandbox, + title, + html, +}) => { + const iframe = useRef(null); + const [height, setHeight] = useState(undefined); + + const handleMessage = useCallback((e: MessageEvent) => { + if (e.data?.type === 'setHeight') { + setHeight(e.data?.height); + } + }, []); + + useEffect(() => { + const iframeDocument = iframe.current?.contentWindow?.document; + + if (iframeDocument && html) { + iframeDocument.open(); + iframeDocument.write(html); + iframeDocument.close(); + iframeDocument.body.style.margin = '0'; + + iframe.current?.contentWindow?.addEventListener('message', handleMessage); + + const innerFrame = iframeDocument.querySelector('iframe'); + if (innerFrame) { + innerFrame.width = '100%'; + } + } + + return () => { + iframe.current?.contentWindow?.removeEventListener('message', handleMessage); + }; + }, [iframe.current, html]); + + return ( +