import { onDebug } from '@atlassian/clientside-extensions-debug'; import { useCallback, useRef } from 'react'; const copyAttributes = ['type', 'src', 'nonce', 'noModule'] as const; const evaluateScripts = (originalScript: HTMLScriptElement) => { const script = document.createElement('script'); script.text = originalScript.text; copyAttributes.forEach((attr) => { // we cant just check if an attribute exists, as some of them are enforced to be "properties" // see: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce if (originalScript[attr]) { // @ts-expect-error We want to assign whatever here, typescript be quiet! script[attr] = originalScript[attr]!; } }); try { document.head.appendChild(script); } catch (e) { onDebug(({ error }) => ({ level: error, message: `An inline script in a WebPanel failed to execute.`, meta: { error: e, }, })); } try { script.remove(); } catch (e) { onDebug(({ error }) => ({ level: error, message: `Failed to remove inline script associated with WebPanel.`, meta: { error: e, }, })); } }; const injectAndEvaluateInlineScripts = (node: HTMLElement, html: string) => { const template = document.createElement('template'); template.innerHTML = html; const scripts = template.content.querySelectorAll('script'); scripts.forEach((script) => script.remove()); node.appendChild(template.content); scripts.forEach((script) => evaluateScripts(script)); }; type WebpanelRootNode = { __cse_webpanel_initialized: boolean } & HTMLElement; const useWebPanelRenderer = (html: string) => { const ref = useRef(null); const setRef = useCallback<(node: HTMLElement | null) => void>( (node) => { // eslint-disable-next-line no-underscore-dangle if (ref?.current?.__cse_webpanel_initialized) { return; } // where the actual work happens. if (node) { injectAndEvaluateInlineScripts(node, html); // eslint-disable-next-line no-underscore-dangle (node as WebpanelRootNode).__cse_webpanel_initialized = true; } // @ts-expect-error This is typed as a "read-only" property, but in fact it can be set. ref.current = node; }, [html], ); return setRef; }; export default useWebPanelRenderer;