Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at post-text-option 187 lines 5.1 kB view raw
1import React, {useEffect, useState} from 'react' 2import {ActivityIndicator, Platform, View} from 'react-native' 3import ReactNativeDeviceAttest from 'react-native-device-attest' 4import {msg} from '@lingui/macro' 5import {useLingui} from '@lingui/react' 6import {nanoid} from 'nanoid/non-secure' 7 8import {createFullHandle} from '#/lib/strings/handles' 9import {logger} from '#/logger' 10import {isAndroid, isIOS, isNative, isWeb} from '#/platform/detection' 11import {useSignupContext} from '#/screens/Signup/state' 12import {CaptchaWebView} from '#/screens/Signup/StepCaptcha/CaptchaWebView' 13import {atoms as a, useTheme} from '#/alf' 14import {FormError} from '#/components/forms/FormError' 15import {GCP_PROJECT_ID} from '#/env' 16import {BackNextButtons} from '../BackNextButtons' 17 18const CAPTCHA_PATH = 19 isWeb || GCP_PROJECT_ID === 0 ? '/gate/signup' : '/gate/signup/attempt-attest' 20 21export function StepCaptcha() { 22 if (isWeb) { 23 return <StepCaptchaInner /> 24 } else { 25 return <StepCaptchaNative /> 26 } 27} 28 29export function StepCaptchaNative() { 30 const [token, setToken] = useState<string>() 31 const [payload, setPayload] = useState<string>() 32 const [ready, setReady] = useState(false) 33 34 useEffect(() => { 35 ;(async () => { 36 logger.debug('trying to generate attestation token...') 37 try { 38 if (isIOS) { 39 logger.debug('starting to generate devicecheck token...') 40 const token = await ReactNativeDeviceAttest.getDeviceCheckToken() 41 setToken(token) 42 logger.debug(`generated devicecheck token: ${token}`) 43 } else { 44 const {token, payload} = 45 await ReactNativeDeviceAttest.getIntegrityToken('signup') 46 setToken(token) 47 setPayload(base64UrlEncode(payload)) 48 } 49 } catch (e: any) { 50 logger.error(e) 51 } finally { 52 setReady(true) 53 } 54 })() 55 }, []) 56 57 if (!ready) { 58 return <View /> 59 } 60 61 return <StepCaptchaInner token={token} payload={payload} /> 62} 63 64function StepCaptchaInner({ 65 token, 66 payload, 67}: { 68 token?: string 69 payload?: string 70}) { 71 const t = useTheme() 72 const {_} = useLingui() 73 const theme = useTheme() 74 const {state, dispatch} = useSignupContext() 75 76 const [completed, setCompleted] = React.useState(false) 77 78 const stateParam = React.useMemo(() => nanoid(15), []) 79 const url = React.useMemo(() => { 80 const newUrl = new URL(state.serviceUrl) 81 newUrl.pathname = CAPTCHA_PATH 82 newUrl.searchParams.set( 83 'handle', 84 createFullHandle(state.handle, state.userDomain), 85 ) 86 newUrl.searchParams.set('state', stateParam) 87 newUrl.searchParams.set('colorScheme', theme.name) 88 89 if (isNative && token) { 90 newUrl.searchParams.set('platform', Platform.OS) 91 newUrl.searchParams.set('token', token) 92 if (isAndroid && payload) { 93 newUrl.searchParams.set('payload', payload) 94 } 95 } 96 97 return newUrl.href 98 }, [ 99 state.serviceUrl, 100 state.handle, 101 state.userDomain, 102 stateParam, 103 theme.name, 104 token, 105 payload, 106 ]) 107 108 const onSuccess = React.useCallback( 109 (code: string) => { 110 setCompleted(true) 111 logger.metric('signup:captchaSuccess', {}, {statsig: true}) 112 dispatch({ 113 type: 'submit', 114 task: {verificationCode: code, mutableProcessed: false}, 115 }) 116 }, 117 [dispatch], 118 ) 119 120 const onError = React.useCallback( 121 (error?: unknown) => { 122 dispatch({ 123 type: 'setError', 124 value: _(msg`Error receiving captcha response.`), 125 }) 126 logger.metric('signup:captchaFailure', {}, {statsig: true}) 127 logger.error('Signup Flow Error', { 128 registrationHandle: state.handle, 129 error, 130 }) 131 }, 132 [_, dispatch, state.handle], 133 ) 134 135 const onBackPress = React.useCallback(() => { 136 logger.error('Signup Flow Error', { 137 errorMessage: 138 'User went back from captcha step. Possibly encountered an error.', 139 registrationHandle: state.handle, 140 }) 141 142 dispatch({type: 'prev'}) 143 }, [dispatch, state.handle]) 144 145 return ( 146 <> 147 <View style={[a.gap_lg, a.pt_lg]}> 148 <View 149 style={[ 150 a.w_full, 151 a.overflow_hidden, 152 {minHeight: 510}, 153 completed && [a.align_center, a.justify_center], 154 ]}> 155 {!completed ? ( 156 <CaptchaWebView 157 url={url} 158 stateParam={stateParam} 159 state={state} 160 onComplete={() => setCompleted(true)} 161 onSuccess={onSuccess} 162 onError={onError} 163 /> 164 ) : ( 165 <ActivityIndicator size="large" color={t.palette.primary_500} /> 166 )} 167 </View> 168 <FormError error={state.error} /> 169 </View> 170 <BackNextButtons 171 hideNext 172 isLoading={state.isLoading} 173 onBackPress={onBackPress} 174 /> 175 </> 176 ) 177} 178 179function base64UrlEncode(data: string): string { 180 const encoder = new TextEncoder() 181 const bytes = encoder.encode(data) 182 183 const binaryString = String.fromCharCode(...bytes) 184 const base64 = btoa(binaryString) 185 186 return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/[=]/g, '') 187}