mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at thread-bug 105 lines 2.7 kB view raw
1import {useEffect, useMemo, useRef} from 'react' 2import {WebView, type WebViewNavigation} from 'react-native-webview' 3import {type ShouldStartLoadRequest} from 'react-native-webview/lib/WebViewTypes' 4 5import {type SignupState} from '#/screens/Signup/state' 6 7const ALLOWED_HOSTS = [ 8 'bsky.social', 9 'bsky.app', 10 'staging.bsky.app', 11 'staging.bsky.dev', 12 'app.staging.bsky.dev', 13 'js.hcaptcha.com', 14 'newassets.hcaptcha.com', 15 'api2.hcaptcha.com', 16] 17 18const MIN_DELAY = 3_500 19 20export function CaptchaWebView({ 21 url, 22 stateParam, 23 state, 24 onSuccess, 25 onError, 26}: { 27 url: string 28 stateParam: string 29 state?: SignupState 30 onSuccess: (code: string) => void 31 onError: (error: unknown) => void 32}) { 33 const startedAt = useRef(Date.now()) 34 const successTo = useRef<NodeJS.Timeout>() 35 36 useEffect(() => { 37 return () => { 38 if (successTo.current) { 39 clearTimeout(successTo.current) 40 } 41 } 42 }, []) 43 44 const redirectHost = useMemo(() => { 45 if (!state?.serviceUrl) return 'bsky.app' 46 47 return state?.serviceUrl && 48 new URL(state?.serviceUrl).host === 'staging.bsky.dev' 49 ? 'app.staging.bsky.dev' 50 : 'bsky.app' 51 }, [state?.serviceUrl]) 52 53 const wasSuccessful = useRef(false) 54 55 const onShouldStartLoadWithRequest = (event: ShouldStartLoadRequest) => { 56 const urlp = new URL(event.url) 57 return ALLOWED_HOSTS.includes(urlp.host) 58 } 59 60 const onNavigationStateChange = (e: WebViewNavigation) => { 61 if (wasSuccessful.current) return 62 63 const urlp = new URL(e.url) 64 if (urlp.host !== redirectHost || urlp.pathname === '/gate/signup') return 65 66 const code = urlp.searchParams.get('code') 67 if (urlp.searchParams.get('state') !== stateParam || !code) { 68 onError({error: 'Invalid state or code'}) 69 return 70 } 71 72 // We want to delay the completion of this screen ever so slightly so that it doesn't appear to be a glitch if it completes too fast 73 wasSuccessful.current = true 74 const now = Date.now() 75 const timeTaken = now - startedAt.current 76 if (timeTaken < MIN_DELAY) { 77 successTo.current = setTimeout(() => { 78 onSuccess(code) 79 }, MIN_DELAY - timeTaken) 80 } else { 81 onSuccess(code) 82 } 83 } 84 85 return ( 86 <WebView 87 source={{uri: url}} 88 javaScriptEnabled 89 style={{ 90 flex: 1, 91 backgroundColor: 'transparent', 92 borderRadius: 10, 93 }} 94 onShouldStartLoadWithRequest={onShouldStartLoadWithRequest} 95 onNavigationStateChange={onNavigationStateChange} 96 scrollEnabled={false} 97 onError={e => { 98 onError(e.nativeEvent) 99 }} 100 onHttpError={e => { 101 onError(e.nativeEvent) 102 }} 103 /> 104 ) 105}