2d313d806f
React Compiler cannot lower a conditional expression - `&&`, `||`, `??`, `?.`, a ternary - inside a try block. Three techniques, picked per site: - split `if (a && b)` into nested ifs, where there is no `else` to break - hoist the expression into a const above the try, where it does not depend on anything the try produces - move it into a module-scope helper, where it does Optional calls become `if (f) f()`, which keeps the arguments unevaluated when the callback is absent, exactly as `f?.()` does. Skipped components: 125 -> 107. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
84 lines
2.2 KiB
TypeScript
84 lines
2.2 KiB
TypeScript
import {useCallback, useEffect} from 'react'
|
|
import {StyleSheet} from 'react-native'
|
|
|
|
import {type CaptchaWebViewProps} from './CaptchaWebView.shared'
|
|
|
|
const REDIRECT_HOST = new URL(window.location.href).host
|
|
|
|
/**
|
|
* Module scope because React Compiler cannot lower an optional chain inside a
|
|
* `try`, and this one has to stay in the `try` - reading `location` on a
|
|
* cross-origin frame throws.
|
|
*/
|
|
function getFrameHref(frame: HTMLIFrameElement | null): string | undefined {
|
|
return frame?.contentWindow?.location.href
|
|
}
|
|
|
|
export function CaptchaWebView({
|
|
url,
|
|
stateParam,
|
|
onSuccess,
|
|
onError,
|
|
}: CaptchaWebViewProps) {
|
|
useEffect(() => {
|
|
const timeout = setTimeout(() => {
|
|
onError({
|
|
errorMessage: 'User did not complete the captcha within 30 seconds',
|
|
})
|
|
}, 30e3)
|
|
|
|
return () => {
|
|
clearTimeout(timeout)
|
|
}
|
|
}, [onError])
|
|
|
|
const onLoad = useCallback(() => {
|
|
const frame: HTMLIFrameElement = document.getElementById(
|
|
'captcha-iframe',
|
|
) as HTMLIFrameElement
|
|
|
|
try {
|
|
const href = getFrameHref(frame)
|
|
if (!href) return
|
|
const urlp = new URL(href)
|
|
|
|
// This shouldn't happen with CORS protections, but for good measure
|
|
if (urlp.host !== REDIRECT_HOST) return
|
|
|
|
const code = urlp.searchParams.get('code')
|
|
const stateMismatch = urlp.searchParams.get('state') !== stateParam
|
|
if (stateMismatch) {
|
|
onError({error: 'Invalid state or code'})
|
|
return
|
|
}
|
|
if (!code) {
|
|
onError({error: 'Invalid state or code'})
|
|
return
|
|
}
|
|
onSuccess(code)
|
|
} catch (e) {
|
|
// We don't actually want to record an error here, because this will happen quite a bit. We will only be able to
|
|
// get the href of the iframe if it's on our domain, so all the hcaptcha requests will throw here, although it's
|
|
// harmless. Our other indicators of time-to-complete and back press should be more reliable in catching issues.
|
|
}
|
|
}, [stateParam, onSuccess, onError])
|
|
|
|
return (
|
|
<iframe
|
|
src={url}
|
|
style={styles.iframe}
|
|
id="captcha-iframe"
|
|
onLoad={onLoad}
|
|
/>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
iframe: {
|
|
flex: 1,
|
|
borderWidth: 0,
|
|
borderRadius: 10,
|
|
backgroundColor: 'transparent',
|
|
},
|
|
})
|