UNPKG

@marsidev/react-turnstile

Version:

Cloudflare Turnstile integration for React.

157 lines (153 loc) 4.98 kB
import React, { forwardRef, useState, useRef, useImperativeHandle, useEffect } from 'react'; const SCRIPT_URL = "https://challenges.cloudflare.com/turnstile/v0/api.js"; const DEFAULT_SCRIPT_ID = "cf-turnstile-script"; const DEFAULT_ONLOAD_NAME = "onloadTurnstileCallback"; const DEFAULT_CONTAINER_ID = "cf-turnstile"; const isScriptInjected = (scriptId) => !!document.querySelector(`#${scriptId}`); const injectTurnstileScript = ({ render, onLoadCallbackName, onLoad, scriptOptions: { nonce = "", defer = true, async = true, id = "", appendTo } = {} }) => { const scriptId = id || DEFAULT_SCRIPT_ID; if (isScriptInjected(scriptId)) { onLoad(); return; } const js = document.createElement("script"); js.id = scriptId; const params = { render: render === "explicit" ? render : "", onload: render === "explicit" ? onLoadCallbackName : "" }; const searchParams = new URLSearchParams(params); js.src = `${SCRIPT_URL}?${searchParams}`; if (nonce) { js.nonce = nonce; } js.defer = !!defer; js.async = !!async; js.onload = onLoad; const elementToInjectScript = appendTo === "body" ? document.body : document.getElementsByTagName("head")[0]; elementToInjectScript.appendChild(js); }; const Turnstile = forwardRef((props, ref) => { const { scriptOptions, options, siteKey, onSuccess, onExpire, onError, id, ...divProps } = props; const config = options ?? {}; const [widgetId, setWidgetId] = useState(); const [scriptLoaded, setScriptLoaded] = useState(false); const containerRef = useRef(null); const firstRendered = useRef(false); const containerId = id ?? DEFAULT_CONTAINER_ID; const onLoadCallbackName = scriptOptions?.onLoadCallbackName || DEFAULT_ONLOAD_NAME; const scriptOptionsJson = JSON.stringify(scriptOptions); const configJson = JSON.stringify(config); useImperativeHandle( ref, () => { const { turnstile } = window; return { getResponse() { if (!window.turnstile || !window.turnstile?.getResponse || !widgetId) { console.warn("Turnstile has not been loaded"); return; } return turnstile.getResponse(widgetId); }, reset() { if (!window.turnstile || !window.turnstile?.reset || !widgetId) { console.warn("Turnstile has not been loaded"); return; } return turnstile.reset(widgetId); }, remove() { if (!window.turnstile || !window.turnstile?.remove || !widgetId) { console.warn("Turnstile has not been loaded"); return; } window.turnstile.remove(widgetId); setWidgetId(""); }, render() { if (!window.turnstile || !window.turnstile?.render) { console.warn("Turnstile has not been loaded"); return; } if (!containerRef.current) { console.warn("Container has not rendered"); return; } if (widgetId) { console.warn("Widget already rendered"); return widgetId; } const id2 = window.turnstile.render(containerRef.current, renderConfig); setWidgetId(id2); return id2; } }; }, [scriptLoaded, window.turnstile, widgetId] ); const renderConfig = { action: config.action, cData: config.cData, theme: config.theme ?? "auto", sitekey: siteKey, tabindex: config.tabIndex, callback: onSuccess, "expired-callback": onExpire, "error-callback": onError }; const onLoadScript = () => { setScriptLoaded(true); }; const onLoadScriptError = () => { console.error("Error loading turnstile script"); }; useEffect(() => { if (!siteKey) { console.warn("sitekey was not provided"); return; } window[onLoadCallbackName] = () => { if (!firstRendered.current) { const id2 = window.turnstile?.render(containerRef.current, renderConfig); setWidgetId(id2); firstRendered.current = true; } }; injectTurnstileScript({ render: "explicit", onLoadCallbackName, scriptOptions, onLoad: onLoadScript, onError: onLoadScriptError }); const timerId = setInterval(() => window.turnstile?.reset(), 250 * 250); return () => { clearInterval(timerId); }; }, [configJson, scriptOptionsJson]); useEffect( function rerenderWidget() { if (containerRef.current && window.turnstile) { const { turnstile } = window; turnstile.remove(widgetId); const id2 = turnstile.render(containerRef.current, renderConfig); setWidgetId(id2); firstRendered.current = true; } }, [configJson, siteKey] ); return /* @__PURE__ */ React.createElement("div", { ref: containerRef, id: containerId, ...divProps }); }); Turnstile.displayName = "Turnstile"; export { Turnstile };