mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-22 19:39:01 -05:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cacedbd03a |
@@ -41,41 +41,113 @@ export const CustomScriptsInjector = ({
|
|||||||
|
|
||||||
if (!scriptsToInject.trim()) return;
|
if (!scriptsToInject.trim()) return;
|
||||||
|
|
||||||
try {
|
/**
|
||||||
// Create a temporary container to parse the HTML
|
* Ensures document.body exists before executing the injection.
|
||||||
const container = document.createElement("div");
|
* This prevents race conditions where custom scripts try to access document.body
|
||||||
container.innerHTML = scriptsToInject;
|
* before React hydration completes, which would cause:
|
||||||
|
* - React error #454 (missing document.body)
|
||||||
// Process and inject script elements
|
* - TypeError: can't access property "removeChild" of null
|
||||||
const scripts = container.querySelectorAll("script");
|
*/
|
||||||
scripts.forEach((script) => {
|
const ensureBodyExists = (): Promise<void> => {
|
||||||
const newScript = document.createElement("script");
|
return new Promise((resolve) => {
|
||||||
|
// If body already exists, resolve immediately
|
||||||
// Copy all attributes (src, async, defer, type, etc.)
|
if (document.body) {
|
||||||
Array.from(script.attributes).forEach((attr) => {
|
resolve();
|
||||||
newScript.setAttribute(attr.name, attr.value);
|
return;
|
||||||
});
|
|
||||||
|
|
||||||
// Copy inline script content
|
|
||||||
if (script.textContent) {
|
|
||||||
newScript.textContent = script.textContent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.head.appendChild(newScript);
|
// Wait for DOMContentLoaded
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", () => resolve(), { once: true });
|
||||||
|
} else {
|
||||||
|
// Document is already loaded but body doesn't exist yet (edge case)
|
||||||
|
// Use setTimeout to defer until next tick
|
||||||
|
setTimeout(() => resolve(), 0);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Process and inject non-script elements (noscript, meta, link, style, etc.)
|
/**
|
||||||
const nonScripts = container.querySelectorAll(":not(script)");
|
* Wraps inline script content to ensure safe execution after DOM is ready.
|
||||||
nonScripts.forEach((el) => {
|
* This prevents scripts from executing before document.body is available.
|
||||||
const clonedEl = el.cloneNode(true) as Element;
|
*/
|
||||||
document.head.appendChild(clonedEl);
|
const wrapScriptContent = (content: string): string => {
|
||||||
});
|
// Don't wrap if the script already has DOM-ready checks
|
||||||
|
if (
|
||||||
|
content.includes("DOMContentLoaded") ||
|
||||||
|
content.includes("document.readyState") ||
|
||||||
|
content.includes("window.addEventListener('load'")
|
||||||
|
) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
injectedRef.current = true;
|
// Wrap the script to ensure it runs after DOM is ready
|
||||||
} catch (error) {
|
return `
|
||||||
// Log error but don't break the survey - self-hosted admins can check console
|
(function() {
|
||||||
console.warn("[Formbricks] Error injecting custom scripts:", error);
|
if (document.readyState === 'loading') {
|
||||||
}
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
${content}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
${content}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const injectScripts = async () => {
|
||||||
|
try {
|
||||||
|
// Wait for document.body to exist before injecting any scripts
|
||||||
|
await ensureBodyExists();
|
||||||
|
|
||||||
|
// Defensive check: ensure body still exists
|
||||||
|
if (!document.body) {
|
||||||
|
console.warn("[Formbricks] document.body is not available, skipping script injection");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a temporary container to parse the HTML
|
||||||
|
const container = document.createElement("div");
|
||||||
|
container.innerHTML = scriptsToInject;
|
||||||
|
|
||||||
|
// Process and inject script elements
|
||||||
|
const scripts = container.querySelectorAll("script");
|
||||||
|
scripts.forEach((script) => {
|
||||||
|
const newScript = document.createElement("script");
|
||||||
|
|
||||||
|
// Copy all attributes (src, async, defer, type, etc.)
|
||||||
|
Array.from(script.attributes).forEach((attr) => {
|
||||||
|
newScript.setAttribute(attr.name, attr.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copy inline script content with safety wrapper
|
||||||
|
if (script.textContent) {
|
||||||
|
// Only wrap inline scripts (not external scripts with src attribute)
|
||||||
|
if (!script.hasAttribute("src")) {
|
||||||
|
newScript.textContent = wrapScriptContent(script.textContent);
|
||||||
|
} else {
|
||||||
|
newScript.textContent = script.textContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.head.appendChild(newScript);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Process and inject non-script elements (noscript, meta, link, style, etc.)
|
||||||
|
const nonScripts = container.querySelectorAll(":not(script)");
|
||||||
|
nonScripts.forEach((el) => {
|
||||||
|
const clonedEl = el.cloneNode(true) as Element;
|
||||||
|
document.head.appendChild(clonedEl);
|
||||||
|
});
|
||||||
|
|
||||||
|
injectedRef.current = true;
|
||||||
|
} catch (error) {
|
||||||
|
// Log error but don't break the survey - self-hosted admins can check console
|
||||||
|
console.warn("[Formbricks] Error injecting custom scripts:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
injectScripts();
|
||||||
}, [projectScripts, surveyScripts, scriptsMode]);
|
}, [projectScripts, surveyScripts, scriptsMode]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user