import React, { useEffect, useRef } from 'react' const providerAdapters = { turnstile: { globalName: 'turnstile', render(api, container, { siteKey, theme, onToken }) { return api.render(container, { sitekey: siteKey, theme, callback: (token) => onToken?.(token || ''), 'expired-callback': () => onToken?.(''), 'error-callback': () => onToken?.(''), }) }, cleanup(api, widgetId, container, onToken) { if (widgetId !== null && api?.remove) { api.remove(widgetId) } if (container) { container.innerHTML = '' } onToken?.('') }, }, recaptcha: { globalName: 'grecaptcha', render(api, container, { siteKey, theme, onToken }) { return api.render(container, { sitekey: siteKey, theme, callback: (token) => onToken?.(token || ''), 'expired-callback': () => onToken?.(''), 'error-callback': () => onToken?.(''), }) }, cleanup(api, widgetId, container, onToken) { if (widgetId !== null && api?.reset) { api.reset(widgetId) } if (container) { container.innerHTML = '' } onToken?.('') }, }, hcaptcha: { globalName: 'hcaptcha', render(api, container, { siteKey, theme, onToken }) { return api.render(container, { sitekey: siteKey, theme, callback: (token) => onToken?.(token || ''), 'expired-callback': () => onToken?.(''), 'error-callback': () => onToken?.(''), }) }, cleanup(api, widgetId, container, onToken) { if (widgetId !== null && api?.remove) { api.remove(widgetId) } if (container) { container.innerHTML = '' } onToken?.('') }, }, } function loadCaptchaScript(src) { if (!src) { return Promise.resolve() } if (!window.__skinbaseCaptchaScripts) { window.__skinbaseCaptchaScripts = {} } if (!window.__skinbaseCaptchaScripts[src]) { window.__skinbaseCaptchaScripts[src] = new Promise((resolve, reject) => { const existing = document.querySelector(`script[src="${src}"]`) if (existing) { if (existing.dataset.loaded === 'true') { resolve() return } existing.addEventListener('load', () => resolve(), { once: true }) existing.addEventListener('error', () => reject(new Error(`Failed to load captcha script: ${src}`)), { once: true }) return } const script = document.createElement('script') script.src = src script.async = true script.defer = true script.addEventListener('load', () => { script.dataset.loaded = 'true' resolve() }, { once: true }) script.addEventListener('error', () => reject(new Error(`Failed to load captcha script: ${src}`)), { once: true }) document.head.appendChild(script) }) } return window.__skinbaseCaptchaScripts[src] } export default function TurnstileField({ provider = 'turnstile', siteKey, scriptUrl = '', onToken, theme = 'dark', className = '' }) { const containerRef = useRef(null) const widgetIdRef = useRef(null) useEffect(() => { const adapter = providerAdapters[provider] || providerAdapters.turnstile if (!siteKey || !containerRef.current) { return undefined } let cancelled = false let intervalId = null const mountWidget = () => { const api = window[adapter.globalName] if (cancelled || !api?.render || widgetIdRef.current !== null) { return } widgetIdRef.current = adapter.render(api, containerRef.current, { siteKey, theme, onToken, }) } loadCaptchaScript(scriptUrl).catch(() => onToken?.('')).finally(() => { const api = window[adapter.globalName] if (typeof api?.ready === 'function') { api.ready(mountWidget) } else { mountWidget() } if (widgetIdRef.current === null) { intervalId = window.setInterval(mountWidget, 250) } }) return () => { cancelled = true if (intervalId) { window.clearInterval(intervalId) } adapter.cleanup(window[adapter.globalName], widgetIdRef.current, containerRef.current, onToken) widgetIdRef.current = null } }, [className, onToken, provider, scriptUrl, siteKey, theme]) if (!siteKey) { return null } return
}