diff --git a/packages/page-js/src/Playground.tsx b/packages/page-js/src/Playground.tsx index 5cc8afe795..7fa588e831 100644 --- a/packages/page-js/src/Playground.tsx +++ b/packages/page-js/src/Playground.tsx @@ -66,15 +66,21 @@ function setupInjected ({ api, isDevelopment }: ApiProps, setIsRunning: (isRunni uiKeyring: isDevelopment ? uiKeyring : null, - util + util, + window }; } +interface IframeWithInjected extends HTMLIFrameElement { + contentWindow: (Window & { injected: Injected }); +} + // FIXME This... ladies & gentlemen, is a mess that should be untangled function Playground ({ basePath, className = '' }: Props): React.ReactElement { const { t } = useTranslation(); const apiProps = useApi(); const injectedRef = useRef(null); + const iframeRef = useRef(null); const [code, setCode] = useState(''); const [isCustomExample, setIsCustomExample] = useState(false); const [isRunning, setIsRunning] = useState(false); @@ -158,24 +164,55 @@ function Playground ({ basePath, className = '' }: Props): React.ReactElement { try { ${code} \n } catch (error) { console.error(error); setIsRunning(false); } })(injected);`; + if (iframeRef.current?.contentWindow) { + const iframeDoc = iframeRef.current.contentWindow.document; - // eslint-disable-next-line no-new-func,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-implied-eval - new Function('injected', exec).bind({}, injectedRef.current)(); + iframeDoc.open(); + iframeDoc.write(''); + iframeDoc.close(); + + // Expose injected to iframe window + iframeRef.current.contentWindow.injected = injectedRef.current; + + // Build destructured keys from injectedRef + const injectedKeys = Object.keys(iframeRef.current.contentWindow.injected).sort().slice(1).join(', '); + + // Build the code to run (scoped with destructured injected keys) + const exec = ` + (async ({ ${injectedKeys} }) => { + try { + ${code} + } catch (error) { + console.error(error); + } finally { + if (typeof setIsRunning === 'function') { + setIsRunning(false); + } + } + })(injected); + `; + + // Create script tag to run code + const bridgeScript = iframeDoc.createElement('script'); + + // eslint-disable-next-line no-new-func, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-implied-eval, @typescript-eslint/no-unsafe-assignment + bridgeScript.innerText = new Function('injected', exec).bind({}, iframeRef.current.contentWindow.injected)(); + + iframeRef.current.contentWindow.document.body.appendChild(bridgeScript); + } else { + throw new Error('No window found to run code'); + } } catch (error) { - injectedRef.current.console.error(error); - } + if (injectedRef.current) { + injectedRef.current.console.error(error); + } - setIsRunning(false); + setIsRunning(false); + } } run().catch(console.error); - }, - [_clearConsole, _hookConsole, apiProps, code] - ); + }, [_clearConsole, _hookConsole, apiProps, code]); const _selectExample = useCallback( (value: string): void => { @@ -261,6 +298,11 @@ function Playground ({ basePath, className = '' }: Props): React.ReactElement +