Integrations

CaptchaAI ile React Native WebView'larda CAPTCHA'ları Çözme

React Native uygulamanızın react-native-webview ile yüklediği üçüncü taraf bir formda reCAPTCHA v2 onay kutusu veya Cloudflare Turnstile widget'ı çıkıyorsa, mobil akış tam orada tıkanır — kullanıcı doğrulamayı tamamlayamaz, form gönderilmez. Çözüm üç adımda özetlenebilir: sitekey'i JavaScript enjeksiyonuyla DOM'dan çıkarın, arka ucunuzdan CaptchaAI API'sine gönderin, dönen token'ı WebView'a geri enjekte edin. Bu rehberde her adımı çalışan React Native ve Node.js koduyla kuruyoruz; sonunda kendi uygulamanıza kopyalayıp uyarlayabileceğiniz uçtan uca bir örnek elinizde olacak.

CaptchaAI reCAPTCHA v2 ve Cloudflare Turnstile dahil CAPTCHA doğrulamalarını API üzerinden çözer; mobil uygulamanız kullanıcıdan manuel işlem beklemek yerine çözülen token'ı gerçek zamanlı enjekte eder. Akış üç katmanda çalışır:

  • React Native WebView — CAPTCHA'yı algılar, sitekey'i çıkarır, çözülen token'ı enjekte eder.
  • Node.js arka uç — sitekey ve sayfa URL'sini alır, CaptchaAI'yi çağırır, token'ı döndürür.
  • CaptchaAI API — CAPTCHA'yı çözer, token üretir.

WebView, window.ReactNativeWebView.postMessage() üzerinden React Native tarafınızla konuşur; API anahtarınızı istemci koduna hiç koymayın. Bu mimari Expo SDK 49+ managed workflow'da expo-dev-client ile de aynı şekilde çalışır.

Örnek Senaryo: Ödeme Formunda CAPTCHA Engeli

Diyelim ki Upwork üzerinden aldığınız bir freelance işte, bir e-ticaret müşterisinin React Native uygulamasına üçüncü taraf bir ödeme sağlayıcısının formunu WebView içinde gömüyorsunuz. Form, gönderilmeden önce tamamlanması gereken bir reCAPTCHA v2 onay kutusu içeriyor. Hedefiniz WebView yüklemeyi bitirdiğinde CAPTCHA widget'ını algılamak, sitekey'i DOM'dan çıkarmak, arka uç servisinden CaptchaAI API'siyle çözmek ve token'ı WebView'a geri enjekte edip formu göndermek.

Ortam: React Native 0.72+, react-native-webview 13+, Node.js arka uç, CaptchaAI API. Kullanıcı verisi topluyorsanız KVKK kapsamındaki yükümlülükleri unutmayın; CaptchaAI entegrasyonunu yalnızca yetkilendirilmiş form akışlarında kullanın. Üretime çıkmadan önce akışı staging.example.com/qa-form gibi bir test formunda doğrulayın.

Adım 1: WebView İçinde CAPTCHA'yı Algılayın ve sitekey'i Çıkarın

Sayfa yüklendikten sonra CAPTCHA öğelerini taramak için injectedJavaScript prop'unu kullanın; aşağıdaki script hem reCAPTCHA v2 hem Cloudflare Turnstile'ı tek geçişte kontrol eder ve sonucu postMessage ile React Native tarafına iletir:

// 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>
  );
}

Adım 2: Node.js Arka Uç Çözücüyü Kurun

CaptchaAI API anahtarınızı yalnızca sunucu tarafında tutun. Arka uç sitekey ve sayfa URL'sini alır, CaptchaAI'ye gönderir, sonucu sorgular ve token'ı döndürür; bu ayrım sayesinde anahtar hiçbir zaman istemci trafiğinde veya uygulama ikili dosyasında görünmez:

// 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'));

Adım 3: WebView'da Token Süresinin Dolmasını Yönetin

Token'ların süresi dolar — reCAPTCHA v2 token'ları ~120 saniye, Turnstile token'ları ~300 saniye geçerlidir. Kullanıcı form gönderimini geciktirirse, göndermeden önce yeniden çözün:

// 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;
  `);
};

Pratikte reCAPTCHA v2 çözümleri 10–20 saniye, Cloudflare Turnstile ise genelde 5–15 saniye içinde tamamlanır; yükleme göstergenizi bu pencereye göre tasarlayın. Aynı sayfada hem reCAPTCHA hem Turnstile varsa, algılama script'ini tüm sitekey'leri toplayacak şekilde genişletin ve token'ları sırayla enjekte edin. WebView cihazın varsayılan mobil User-Agent'ını kullanır ve bu genelde ek ayar gerektirmeden istikrarlı sonuç verir; iOS'ta WKWebView bazı content security policy kurallarını daha sıkı uyguladığından, enjeksiyon script'ini try/catch içine almak orada daha kritiktir.

Sorun Giderme

Aşağıda en sık karşılaşılan beş hata, nedeni ve hızlı çözümü listelenmiştir:

  • postMessage alınmıyor — WebView onMessage bağlı değil veya script hata veriyor → onMessage'in bağlı olduğunu doğrulayın, enjeksiyon script'ini try/catch içine alın.
  • ERROR_BAD_TOKEN_OR_PAGEURL — sitekey sayfa URL'siyle eşleşmiyor → sitekey'i üst sayfadan değil, gerçek iframe kaynağından çıkarın.
  • Token enjeksiyonu callback'i tetiklemiyor — reCAPTCHA callback'i ___grecaptcha_cfg içinde bulunamadı → tüm client nesnelerini dolaşın, callback fonksiyonları için iç içe alanları kontrol edin.
  • CAPCHA_NOT_READY sürekli dönüyor — yavaş çözüm veya geçersiz parametreler → sorgulama zaman aşımını artırın, sitekey ve sayfa URL'sinin doğru olduğunu doğrulayın.
  • WebView boş CAPTCHA gösteriyor — JavaScript devre dışı veya içerik engelleniyor → javaScriptEnabled={true} ayarlayın, content security policy engeli olmadığından emin olun.

SSS

API anahtarımı doğrudan React Native'den kullanabilir miyim?

Teknik olarak evet, ama anahtar uygulama ikili dosyasında görünür hâle gelir ve tersine mühendislikle çıkarılabilir. CaptchaAI çağrılarını her zaman kendi arka ucunuzdan yönlendirin, anahtarı istemci koduna hiç yazmayın ve ortam değişkeni olarak saklayın.

Mobil trafiğim için hangi CaptchaAI planı yeterli?

Trafiğinize göre önerilen üç kademeli seçenek şöyledir:

  • BASIC ($15/ay, 5 thread) — çoğu orta ölçekli entegrasyon için başlangıç
  • STANDARD ($30/ay, 15 thread) veya ADVANCE ($90/ay, 50 thread) — eşzamanlı çözüm hacmi arttıkça
  • CORPORATE ($240/ay, 150 thread) veya ENTERPRISE ($300/ay, 200 thread) — yoğun kampanya dönemlerinde

Plan thread sayısına göre faturalandırılır, thread başına çözüm sınırsızdır.

Token süresi dolarsa kullanıcı ne görür?

Form gönderimi başarısız olur veya sunucu tarafında doğrulama hatası döner; kullanıcı genelde belirsiz bir hata mesajı görür. Bu yüzden gönderim anında token yaşını kontrol edip gerekirse yeniden çözmek (Adım 3'teki gibi) zaman aşımı hatalarını önler ve kullanıcı deneyimini bozmaz.

İlgili Makaleler ve Sonraki Adımlar

React Native uygulamalarınızda CAPTCHA çözümüne bugün başlayın — CaptchaAI API anahtarınızı alın, yukarıdaki arka uç çözücüyü kendi sunucunuza entegre edin ve akışı staging ortamında doğruladıktan sonra üretime taşıyın. Konuyu derinleştirmek için:

İlgili kılavuzlar:

Bu makale için yorumlar devre dışı bırakılmıştır.