my fork of the bluesky client

sentry errors for captcha web views and registration attempts (#3761)

* sentry errors for captcha web views

* include handles with errors

* log all registration request failures

* rm

* use a better trigger for web captcha errors

* add another trigger for recording a possible signup error

* unknown error type

* don't needlessly log on href errors

* honestly i probably cant always do a captcha in 20 seconds

* rm log

* timeout on back

* remove unnecessary colons

authored by hailey.at and committed by

GitHub b8d8bec3 81ae7e42

+60 -22
+8 -2
src/screens/Signup/StepCaptcha/CaptchaWebView.tsx
··· 26 26 stateParam: string 27 27 state?: SignupState 28 28 onSuccess: (code: string) => void 29 - onError: () => void 29 + onError: (error: unknown) => void 30 30 }) { 31 31 const redirectHost = React.useMemo(() => { 32 32 if (!state?.serviceUrl) return 'bsky.app' ··· 56 56 57 57 const code = urlp.searchParams.get('code') 58 58 if (urlp.searchParams.get('state') !== stateParam || !code) { 59 - onError() 59 + onError({error: 'Invalid state or code'}) 60 60 return 61 61 } 62 62 ··· 74 74 onShouldStartLoadWithRequest={onShouldStartLoadWithRequest} 75 75 onNavigationStateChange={onNavigationStateChange} 76 76 scrollEnabled={false} 77 + onError={e => { 78 + onError(e.nativeEvent) 79 + }} 80 + onHttpError={e => { 81 + onError(e.nativeEvent) 82 + }} 77 83 /> 78 84 ) 79 85 }
+18 -4
src/screens/Signup/StepCaptcha/CaptchaWebView.web.tsx
··· 13 13 url: string 14 14 stateParam: string 15 15 onSuccess: (code: string) => void 16 - onError: () => void 16 + onError: (error: unknown) => void 17 17 }) { 18 + React.useEffect(() => { 19 + const timeout = setTimeout(() => { 20 + onError({ 21 + errorMessage: 'User did not complete the captcha within 30 seconds', 22 + }) 23 + }, 30e3) 24 + 25 + return () => { 26 + clearTimeout(timeout) 27 + } 28 + }, [onError]) 29 + 18 30 const onLoad = React.useCallback(() => { 19 31 // @ts-ignore web 20 32 const frame: HTMLIFrameElement = document.getElementById( ··· 32 44 33 45 const code = urlp.searchParams.get('code') 34 46 if (urlp.searchParams.get('state') !== stateParam || !code) { 35 - onError() 47 + onError({error: 'Invalid state or code'}) 36 48 return 37 49 } 38 50 onSuccess(code) 39 - } catch (e) { 40 - // We don't need to handle this 51 + } catch (e: unknown) { 52 + // We don't actually want to record an error here, because this will happen quite a bit. We will only be able to 53 + // get hte href of the iframe if it's on our domain, so all the hcaptcha requests will throw here, although it's 54 + // harmless. Our other indicators of time-to-complete and back press should be more reliable in catching issues. 41 55 } 42 56 }, [stateParam, onSuccess, onError]) 43 57
+14 -6
src/screens/Signup/StepCaptcha/index.tsx
··· 5 5 import {nanoid} from 'nanoid/non-secure' 6 6 7 7 import {createFullHandle} from '#/lib/strings/handles' 8 + import {logger} from '#/logger' 8 9 import {ScreenTransition} from '#/screens/Login/ScreenTransition' 9 10 import {useSignupContext, useSubmitSignup} from '#/screens/Signup/state' 10 11 import {CaptchaWebView} from '#/screens/Signup/StepCaptcha/CaptchaWebView' ··· 43 44 [submit], 44 45 ) 45 46 46 - const onError = React.useCallback(() => { 47 - dispatch({ 48 - type: 'setError', 49 - value: _(msg`Error receiving captcha response.`), 50 - }) 51 - }, [_, dispatch]) 47 + const onError = React.useCallback( 48 + (error?: unknown) => { 49 + dispatch({ 50 + type: 'setError', 51 + value: _(msg`Error receiving captcha response.`), 52 + }) 53 + logger.error('Signup Flow Error', { 54 + registrationHandle: state.handle, 55 + error, 56 + }) 57 + }, 58 + [_, dispatch, state.handle], 59 + ) 52 60 53 61 return ( 54 62 <ScreenTransition>
+10 -1
src/screens/Signup/index.tsx
··· 8 8 import {FEEDBACK_FORM_URL} from '#/lib/constants' 9 9 import {logEvent} from '#/lib/statsig/statsig' 10 10 import {createFullHandle} from '#/lib/strings/handles' 11 + import {logger} from '#/logger' 11 12 import {useServiceQuery} from '#/state/queries/service' 12 13 import {useAgent} from '#/state/session' 13 14 import {LoggedOutLayout} from '#/view/com/util/layouts/LoggedOutLayout' ··· 119 120 120 121 const onBackPress = React.useCallback(() => { 121 122 if (state.activeStep !== SignupStep.INFO) { 123 + if (state.activeStep === SignupStep.CAPTCHA) { 124 + logger.error('Signup Flow Error', { 125 + errorMessage: 126 + 'User went back from captcha step. Possibly encountered an error.', 127 + registrationHandle: state.handle, 128 + }) 129 + } 130 + 122 131 dispatch({type: 'prev'}) 123 132 } else { 124 133 onPressBack() 125 134 } 126 - }, [onPressBack, state.activeStep]) 135 + }, [onPressBack, state.activeStep, state.handle]) 127 136 128 137 return ( 129 138 <SignupContext.Provider value={{state, dispatch}}>
+10 -9
src/screens/Signup/state.ts
··· 246 246 !verificationCode 247 247 ) { 248 248 dispatch({type: 'setStep', value: SignupStep.CAPTCHA}) 249 + logger.error('Signup Flow Error', { 250 + errorMessage: 'Verification captcha code was not set.', 251 + registrationHandle: state.handle, 252 + }) 249 253 return dispatch({ 250 254 type: 'setError', 251 255 value: _(msg`Please complete the verification captcha.`), ··· 282 286 return 283 287 } 284 288 285 - if ([400, 429].includes(e.status)) { 286 - logger.warn('Failed to create account', {message: e}) 287 - } else { 288 - logger.error(`Failed to create account (${e.status} status)`, { 289 - message: e, 290 - }) 291 - } 292 - 293 289 const error = cleanError(errMsg) 294 290 const isHandleError = error.toLowerCase().includes('handle') 295 291 296 292 dispatch({type: 'setIsLoading', value: false}) 297 - dispatch({type: 'setError', value: cleanError(errMsg)}) 293 + dispatch({type: 'setError', value: error}) 298 294 dispatch({type: 'setStep', value: isHandleError ? 2 : 1}) 295 + 296 + logger.error('Signup Flow Error', { 297 + errorMessage: error, 298 + registrationHandle: state.handle, 299 + }) 299 300 } finally { 300 301 dispatch({type: 'setIsLoading', value: false}) 301 302 }