Las aplicaciones React Native que cargan contenido web a través de react-native-webview frecuentemente encuentran CAPTCHA: casillas de verificación reCAPTCHA v2, widgets Cloudflare Turnstile o páginas de desafío que bloquean el envío de formularios.CaptchaAIresuelve estos desafíos mediante programación para que los flujos de sus aplicaciones móviles permanezcan ininterrumpidos.
Esta guía le muestra cómo detectar CAPTCHA dentro de React Native WebView, extraer los parámetros requeridos mediante inyección de JavaScript, enviarlos a la API CaptchaAI e inyectar el token resuelto nuevamente en la página.
Escenario del mundo real
Está creando una aplicación React Native que carga un formulario web de terceros dentro de un WebView. El formulario incluye una casilla de verificación reCAPTCHA v2 que debe completarse antes de enviarlo. Tu objetivo es:
- Detectar el widget CAPTCHA cuando WebView termine de cargarse
- Extraiga la clave del sitio del DOM
- Resolverlo a través de la API de CaptchaAI desde un servicio backend
- Inyecte el token nuevamente en WebView y envíe el formulario.
Entorno: React Native 0.72+, react-native-webview 13+, backend de Node.js, API CaptchaAI.
Descripción general de la arquitectura
El flujo se divide en tres capas:
| capa | Responsabilidad |
|---|---|
| Reaccionar vista web nativa | Detecta CAPTCHA, extrae la clave del sitio, inyecta el token resuelto |
| API de back-end (Node.js) | Recibe la clave del sitio + pageurl, llama a CaptchaAI y devuelve el token |
| API CaptchaAI | Resuelve el CAPTCHA y devuelve el token |
WebView se comunica con su código React Native a través de window.ReactNativeWebView.postMessage(), y su backend maneja la interacción CaptchaAI para mantener las claves API fuera del cliente.
Paso 1: Detectar CAPTCHA y extraer Sitekey en WebView
Utilice el accesorio injectedJavaScript para buscar elementos CAPTCHA una vez que se carga la página:
// CaptchaDetector.js — React Native Component
import React, { useRef, useState } from 'react';
import { View, ActivityIndicator } from 'react-native';
import { WebView } from 'react-native-webview';
const CAPTCHA_DETECTION_SCRIPT = `
(function() {
// Detect reCAPTCHA v2
const recaptchaDiv = document.querySelector('.g-recaptcha');
if (recaptchaDiv) {
const sitekey = recaptchaDiv.getAttribute('data-sitekey');
window.ReactNativeWebView.postMessage(JSON.stringify({
type: 'captcha_detected',
captchaType: 'recaptcha_v2',
sitekey: sitekey,
pageurl: window.location.href
}));
return;
}
// Detect Cloudflare Turnstile
const turnstileDiv = document.querySelector('.cf-turnstile');
if (turnstileDiv) {
const sitekey = turnstileDiv.getAttribute('data-sitekey');
window.ReactNativeWebView.postMessage(JSON.stringify({
type: 'captcha_detected',
captchaType: 'turnstile',
sitekey: sitekey,
pageurl: window.location.href
}));
return;
}
window.ReactNativeWebView.postMessage(JSON.stringify({
type: 'no_captcha'
}));
})();
true;
`;
export default function CaptchaWebView({ url }) {
const webviewRef = useRef(null);
const [solving, setSolving] = useState(false);
const handleMessage = async (event) => {
const data = JSON.parse(event.nativeEvent.data);
if (data.type === 'captcha_detected') {
setSolving(true);
try {
const token = await solveCaptchaViaBackend(
data.captchaType,
data.sitekey,
data.pageurl
);
injectToken(data.captchaType, token);
} catch (err) {
console.error('CAPTCHA solve failed:', err.message);
} finally {
setSolving(false);
}
}
};
const solveCaptchaViaBackend = async (captchaType, sitekey, pageurl) => {
const response = await fetch('https://your-backend.com/api/solve-captcha', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ captchaType, sitekey, pageurl }),
});
const result = await response.json();
if (!result.token) throw new Error(result.error || 'No token returned');
return result.token;
};
const injectToken = (captchaType, token) => {
let script;
if (captchaType === 'recaptcha_v2') {
script = `
document.getElementById('g-recaptcha-response').value = '${token}';
if (typeof ___grecaptcha_cfg !== 'undefined') {
Object.keys(___grecaptcha_cfg.clients).forEach(key => {
const client = ___grecaptcha_cfg.clients[key];
Object.keys(client).forEach(k => {
const item = client[k];
if (item && item.callback) {
item.callback('${token}');
}
});
});
}
true;
`;
} else if (captchaType === 'turnstile') {
script = `
const input = document.querySelector('[name="cf-turnstile-response"]');
if (input) input.value = '${token}';
const callback = document.querySelector('.cf-turnstile')
?.getAttribute('data-callback');
if (callback && typeof window[callback] === 'function') {
window[callback]('${token}');
}
true;
`;
}
webviewRef.current?.injectJavaScript(script);
};
return (
<View style={{ flex: 1 }}>
{solving && <ActivityIndicator size="large" />}
<WebView
ref={webviewRef}
source={{ uri: url }}
injectedJavaScript={CAPTCHA_DETECTION_SCRIPT}
onMessage={handleMessage}
javaScriptEnabled={true}
/>
</View>
);
}
Paso 2: construir el solucionador de backend (Node.js)
Mantenga su clave API CaptchaAI en el lado del servidor. El backend recibe la clave del sitio y la URL de la página, la envía a CaptchaAI, sondea el resultado y devuelve el token:
// server.js — Express backend
const express = require('express');
const axios = require('axios');
const app = express();
app.use(express.json());
const API_KEY = process.env.CAPTCHAAI_API_KEY || 'YOUR_API_KEY';
app.post('/api/solve-captcha', async (req, res) => {
const { captchaType, sitekey, pageurl } = req.body;
try {
// Step 1: Submit task to CaptchaAI
const submitParams = {
key: API_KEY,
pageurl: pageurl,
json: '1',
};
if (captchaType === 'recaptcha_v2') {
submitParams.method = 'userrecaptcha';
submitParams.googlekey = sitekey;
} else if (captchaType === 'turnstile') {
submitParams.method = 'turnstile';
submitParams.sitekey = sitekey;
}
const submitResponse = await axios.get(
'https://ocr.captchaai.com/in.php',
{ params: submitParams }
);
if (submitResponse.data.status !== 1) {
return res.status(400).json({ error: submitResponse.data.request });
}
const taskId = submitResponse.data.request;
// Step 2: Poll for result
const token = await pollForResult(taskId);
res.json({ token });
} catch (error) {
console.error('Solve error:', error.message);
res.status(500).json({ error: 'Failed to solve CAPTCHA' });
}
});
async function pollForResult(taskId, maxAttempts = 30) {
for (let i = 0; i < maxAttempts; i++) {
await new Promise((r) => setTimeout(r, 5000));
const response = await axios.get('https://ocr.captchaai.com/res.php', {
params: {
key: API_KEY,
action: 'get',
id: taskId,
json: '1',
},
});
if (response.data.status === 1) {
return response.data.request;
}
if (
response.data.request !== 'CAPCHA_NOT_READY' &&
response.data.status === 0
) {
throw new Error(response.data.request);
}
}
throw new Error('Polling timeout — CAPTCHA not solved in time');
}
app.listen(3000, () => console.log('Solver backend running on port 3000'));
Paso 3: Manejar la caducidad del token en WebView
Los tokens CAPTCHA caducan: los tokens reCAPTCHA v2 duran ~120 segundos, los tokens Turnstile ~300 segundos. Si el usuario retrasa el envío del formulario, resuélvalo antes de enviarlo:
// Add to CaptchaWebView component
const [tokenTimestamp, setTokenTimestamp] = useState(null);
const TOKEN_TTL_MS = 110000; // 110 seconds for reCAPTCHA v2
const handleFormSubmit = async (captchaType, sitekey, pageurl) => {
const now = Date.now();
if (!tokenTimestamp || now - tokenTimestamp > TOKEN_TTL_MS) {
const freshToken = await solveCaptchaViaBackend(
captchaType, sitekey, pageurl
);
injectToken(captchaType, freshToken);
setTokenTimestamp(Date.now());
}
webviewRef.current?.injectJavaScript(`
document.querySelector('form').submit();
true;
`);
};
Solución de problemas
| problema | causa | Solución |
|---|---|---|
postMessage no recibido |
WebView onMessage no configurado o error de script |
Verifique que onMessage esté vinculado; ajustar el script de inyección en try/catch |
ERROR_BAD_TOKEN_OR_PAGEURL |
Sitekey no coincide con la URL de la página | Extraiga la clave del sitio del iframe src real, no de la página principal |
| La inyección de token no activa la devolución de llamada | Devolución de llamada reCAPTCHA no encontrada en ___grecaptcha_cfg |
Itere todos los objetos del cliente y verifique las propiedades anidadas para las funciones de devolución de llamada |
CAPCHA_NOT_READY indefinidamente |
Resolución lenta o parámetros no válidos | Aumentar el tiempo de espera de las consultas; verificar que la clave del sitio y la URL de la página sean correctas |
| WebView muestra CAPTCHA en blanco | JavaScript deshabilitado o contenido bloqueado | Establezca javaScriptEnabled={true} y asegúrese de que no haya bloqueos de políticas de seguridad de contenido |
Preguntas frecuentes
¿Puedo llamar a CaptchaAI directamente desde React Native sin un backend?
Técnicamente sí, pero su clave API estaría expuesta en el binario de la aplicación. Dirija siempre las llamadas API a través de su propio backend para mantener las credenciales seguras.
¿Funciona esto con el flujo de trabajo administrado por Expo?
Sí, react-native-webview es compatible con Expo SDK 49+ con expo-dev-client. La inyección de JavaScript y el paso de mensajes funcionan de manera idéntica.
¿Cómo manejo las páginas con reCAPTCHA y Turnstile?
El script de detección comprueba ambos. Si una página tiene varios CAPTCHA, extienda el script para recopilar todas las claves del sitio y resolverlas secuencialmente antes de enviar el formulario.
¿Cuál es el tiempo promedio de resolución de CAPTCHA móviles?
La resolución de reCAPTCHA v2 suele tardar entre 10 y 20 segundos. Cloudflare Turnstile es más rápido, entre 5 y 15 segundos. Planifique su UX para mostrar un indicador de carga durante esta ventana.
¿El User-Agent de WebView afecta las tasas de resolución?
React Native WebView utiliza el agente de usuario móvil predeterminado del dispositivo, que se alinea bien con la forma en que navegan los usuarios reales. Esto generalmente produce buenas tasas de resolución sin modificaciones.
Artículos relacionados
- Cómo resolver la devolución de llamada de Recaptcha V2 usando Api
- Comparación de Geetest y Cloudflare Turnstile
- Torniquete Recaptcha V2 Manejo en el mismo sitio
Próximos pasos
Comience a resolver CAPTCHA en sus aplicaciones React Native:obtenga su clave API CaptchaAIe integre el solucionador backend hoy.
Guías relacionadas:
- Manejo de CAPTCHA en la automatización de aplicaciones móviles con Appium
- Cloudflare Turnstile Extracción de clave de sitio de la fuente de la página
- Extracción de parámetros reCAPTCHA de la fuente de la página