mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
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}