As of recently, users of our iOS app can no longer complete captcha requests using Turnstile. It spinns forever, and after a while also shows the following message:
Verification is taking longer than expected. Check your Internet connection and refresh the page if the issue persists.
This has been observed on multiple iOS devices, on multiple internet connections, in multiple countries. We have yet to find a single iOS device where it works. It does work reliably on Android, and on our web platform.
The following is the code that we are using in our React Native project:
import React, { useCallback } from 'react'
import WebView, { WebViewMessageEvent } from 'react-native-webview'
import { VStack } from 'react-stacked'
import { CLOUDFLARE_TURNSTILE_SITE_KEY } from '../lib/config'
// https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/#widget-size
const size = { width: 300, height: 65 }
const html = `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CAPTCHA</title>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<style>
html, body {
margin: 0;
padding: 0;
}
body {
display: flex;
align-items: center;
justify-content: center;
min-height: ${size.height}px;
min-width: ${size.width}px;
background-image: url("data:image/svg+xml,%3Csvg width='32' height='32' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.a%7Btransform-origin:center;animation:b .6s linear infinite%7D@keyframes b%7B100%25%7Btransform:rotate(360deg)%7D%7D%3C/style%3E%3Cpath class='a' d='M2,12A11.2,11.2,0,0,1,13,1.05C12.67,1,12.34,1,12,1a11,11,0,0,0,0,22c.34,0,.67,0,1-.05C6,23,2,17.74,2,12Z'/%3E%3C/svg%3E");
background-position: center;
background-repeat: no-repeat;
}
</style>
</head>
<body>
<script>
function javascriptCallback (token) {
window.ReactNativeWebView.postMessage(token || '')
}
</script>
<div class="cf-turnstile" data-sitekey="${CLOUDFLARE_TURNSTILE_SITE_KEY}" data-callback="javascriptCallback"></div>
</body>
</html>
`
interface CaptchaViewProps {
onFailure: () => void
onSuccess: (token: string) => void
}
const CaptchaView: React.FC<CaptchaViewProps> = ({ onFailure, onSuccess }) => {
const handleMessage = useCallback((event: WebViewMessageEvent) => {
if (event.nativeEvent.data !== '') {
onSuccess(event.nativeEvent.data)
} else {
onFailure()
}
}, [onFailure, onSuccess])
return (
<VStack {...size}>
<WebView onMessage={handleMessage} source={{ baseUrl: 'https://ourwebapp.com', html }} style={size} />
</VStack>
)
}
export default CaptchaView