forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
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 onComplete,
25 onSuccess,
26 onError,
27}: {
28 url: string
29 stateParam: string
30 state?: SignupState
31 onComplete: () => void
32 onSuccess: (code: string) => void
33 onError: (error: unknown) => void
34}) {
35 const startedAt = useRef(Date.now())
36 const successTo = useRef<NodeJS.Timeout>(undefined)
37
38 useEffect(() => {
39 return () => {
40 if (successTo.current) {
41 clearTimeout(successTo.current)
42 }
43 }
44 }, [])
45
46 const redirectHost = useMemo(() => {
47 if (!state?.serviceUrl) return 'bsky.app'
48
49 return state?.serviceUrl &&
50 new URL(state?.serviceUrl).host === 'staging.bsky.dev'
51 ? 'app.staging.bsky.dev'
52 : 'bsky.app'
53 }, [state?.serviceUrl])
54
55 const wasSuccessful = useRef(false)
56
57 const onShouldStartLoadWithRequest = (event: ShouldStartLoadRequest) => {
58 const urlp = new URL(event.url)
59 return ALLOWED_HOSTS.includes(urlp.host)
60 }
61
62 const onNavigationStateChange = (e: WebViewNavigation) => {
63 if (wasSuccessful.current) return
64
65 const urlp = new URL(e.url)
66 if (urlp.host !== redirectHost || urlp.pathname === '/gate/signup') return
67
68 const code = urlp.searchParams.get('code')
69 if (urlp.searchParams.get('state') !== stateParam || !code) {
70 onError({error: 'Invalid state or code'})
71 return
72 }
73
74 // 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
75 wasSuccessful.current = true
76 onComplete()
77 const now = Date.now()
78 const timeTaken = now - startedAt.current
79 if (timeTaken < MIN_DELAY) {
80 successTo.current = setTimeout(() => {
81 onSuccess(code)
82 }, MIN_DELAY - timeTaken)
83 } else {
84 onSuccess(code)
85 }
86 }
87
88 return (
89 <WebView
90 source={{uri: url}}
91 javaScriptEnabled
92 style={{
93 flex: 1,
94 backgroundColor: 'transparent',
95 borderRadius: 10,
96 }}
97 onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
98 onNavigationStateChange={onNavigationStateChange}
99 scrollEnabled={false}
100 onError={e => {
101 onError(e.nativeEvent)
102 }}
103 onHttpError={e => {
104 onError(e.nativeEvent)
105 }}
106 />
107 )
108}