UNPKG

@valture/react-native-recaptcha-v3

Version:

React Native component for integrating Google reCAPTCHA v3, providing seamless bot protection for mobile applications.

237 lines (228 loc) 9.03 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _react = _interopRequireWildcard(require("react")); var _reactNative = require("react-native"); var _reactNativeWebview = require("react-native-webview"); var _jsxRuntime = require("react/jsx-runtime"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } const ReCaptchaV3 = /*#__PURE__*/(0, _react.forwardRef)(({ siteKey = 'dummy-site-key', baseUrl = 'https://example.com', action = 'submit', onVerify, onError, containerStyle, style }, ref) => { const webViewRef = (0, _react.useRef)(null); const [isReady, setIsReady] = (0, _react.useState)(false); const tokenPromiseRef = (0, _react.useRef)(null); // Queue for token requests that come in before the reCAPTCHA is ready const pendingRequests = (0, _react.useRef)([]); const handleError = _react.default.useCallback(error => { onError?.(error); if (tokenPromiseRef.current) { tokenPromiseRef.current.reject(new Error(error)); tokenPromiseRef.current = null; } }, [onError]); // Handle the case when reCAPTCHA is ready (0, _react.useEffect)(() => { if (isReady && pendingRequests.current.length > 0) { console.log(`Processing ${pendingRequests.current.length} pending reCAPTCHA requests`); // Process all pending requests const requests = [...pendingRequests.current]; pendingRequests.current = []; // Process the first request immediately const firstRequest = requests.shift(); if (firstRequest) { executeReCaptcha(firstRequest.action, firstRequest.resolve, firstRequest.reject); } // Queue the rest with a small delay to prevent overwhelming the WebView requests.forEach((request, index) => { setTimeout(() => { executeReCaptcha(request.action, request.resolve, request.reject); }, (index + 1) * 500); // Stagger requests by 500ms }); } }, [isReady]); // Function to execute reCAPTCHA and get a token const executeReCaptcha = (customAction, resolve, reject) => { if (!isReady) { console.warn('reCAPTCHA not ready yet, queueing request'); pendingRequests.current.push({ action: customAction, resolve, reject }); return; } tokenPromiseRef.current = { resolve, reject, action: customAction }; const jsToInject = ` (function executeReCaptcha() { try { if (window.grecaptcha && window.grecaptcha.execute) { window.grecaptcha.execute('${siteKey}', { action: '${customAction}' }) .then(function(token) { window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'VERIFY', token: token, action: '${customAction}' })); }) .catch(function(error) { window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'ERROR', error: error.message || 'reCAPTCHA execution failed', action: '${customAction}' })); }); } else { window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'ERROR', error: 'reCAPTCHA not ready', action: '${customAction}' })); } } catch (e) { window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'ERROR', error: 'JavaScript execution error: ' + (e.message || 'Unknown error'), action: '${customAction}' })); } return true; })(); `; webViewRef.current?.injectJavaScript(jsToInject); }; (0, _react.useImperativeHandle)(ref, () => ({ getToken: (customAction = action) => { console.log("🚀 ~ useImperativeHandle ~ customAction:", customAction); return new Promise((resolve, reject) => { executeReCaptcha(customAction, resolve, reject); }); }, // Add a new method to check if reCAPTCHA is ready isReady: () => { return isReady; } }), [siteKey, action, isReady]); const htmlContent = ` <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script> // Global error handler window.onerror = function(msg, url, lineNo, columnNo, error) { if (window.ReactNativeWebView && window.ReactNativeWebView.postMessage) { window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'ERROR', error: 'Script error: ' + msg })); } return false; }; // Function to check if reCAPTCHA is ready and notify the React Native app function checkRecaptchaReady() { if (window.grecaptcha && window.grecaptcha.ready) { window.grecaptcha.ready(function() { if (window.ReactNativeWebView && window.ReactNativeWebView.postMessage) { window.ReactNativeWebView.postMessage(JSON.stringify({ type: 'READY' })); } }); } else { setTimeout(checkRecaptchaReady, 500); } } // Start checking for reCAPTCHA readiness immediately setTimeout(checkRecaptchaReady, 500); </script> </head> <body style="background-color: transparent;"> <div id="recaptcha-container"></div> <script src="https://www.google.com/recaptcha/api.js?render=${siteKey}" async defer></script> <script> // Also check readiness when DOM is fully loaded document.addEventListener('DOMContentLoaded', function() { checkRecaptchaReady(); }); </script> </body> </html> `; const handleMessage = _react.default.useCallback(event => { try { const data = JSON.parse(event.nativeEvent.data); if (data.type === 'READY') { console.log('reCAPTCHA is ready'); setIsReady(true); } else if (data.type === 'VERIFY' && data.token) { console.log('reCAPTCHA token received'); onVerify?.(data.token); if (tokenPromiseRef.current) { tokenPromiseRef.current.resolve(data.token); tokenPromiseRef.current = null; } } else if (data.type === 'ERROR') { console.warn('reCAPTCHA error:', data.error); handleError(data.error || 'reCAPTCHA error'); } } catch (error) { console.error('Failed to parse WebView message:', error); handleError('Failed to parse reCAPTCHA response'); } }, [onVerify, handleError]); const handleWebViewError = _react.default.useCallback(syntheticEvent => { const { nativeEvent } = syntheticEvent; console.error('WebView error:', nativeEvent); handleError(`WebView error: ${nativeEvent.description}`); }, [handleError]); const handleWebViewHttpError = _react.default.useCallback(syntheticEvent => { const { nativeEvent } = syntheticEvent; console.error('WebView HTTP error:', nativeEvent); handleError(`WebView HTTP error: ${nativeEvent.statusCode}`); }, [handleError]); return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeWebview.WebView, { ref: webViewRef, source: { html: htmlContent, baseUrl }, onMessage: handleMessage, javaScriptEnabled: true, domStorageEnabled: true, originWhitelist: ['*'], style: [styles.webview, style], automaticallyAdjustContentInsets: true, mixedContentMode: 'always', containerStyle: [styles.container, containerStyle], onError: handleWebViewError, onHttpError: handleWebViewHttpError }); }); const styles = _reactNative.StyleSheet.create({ container: { justifyContent: 'center' }, webview: { backgroundColor: 'transparent' } }); var _default = exports.default = ReCaptchaV3; //# sourceMappingURL=index.js.map