@marsidev/react-turnstile
Version:
Cloudflare Turnstile integration for React.
157 lines (153 loc) • 4.98 kB
JavaScript
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 };