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
zio/stable
Hailey 2024-05-01 01:08:59 -07:00 committed by GitHub
parent 81ae7e425d
commit b8d8bec388
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 60 additions and 22 deletions

View File

@ -26,7 +26,7 @@ export function CaptchaWebView({
stateParam: string stateParam: string
state?: SignupState state?: SignupState
onSuccess: (code: string) => void onSuccess: (code: string) => void
onError: () => void onError: (error: unknown) => void
}) { }) {
const redirectHost = React.useMemo(() => { const redirectHost = React.useMemo(() => {
if (!state?.serviceUrl) return 'bsky.app' if (!state?.serviceUrl) return 'bsky.app'
@ -56,7 +56,7 @@ export function CaptchaWebView({
const code = urlp.searchParams.get('code') const code = urlp.searchParams.get('code')
if (urlp.searchParams.get('state') !== stateParam || !code) { if (urlp.searchParams.get('state') !== stateParam || !code) {
onError() onError({error: 'Invalid state or code'})
return return
} }
@ -74,6 +74,12 @@ export function CaptchaWebView({
onShouldStartLoadWithRequest={onShouldStartLoadWithRequest} onShouldStartLoadWithRequest={onShouldStartLoadWithRequest}
onNavigationStateChange={onNavigationStateChange} onNavigationStateChange={onNavigationStateChange}
scrollEnabled={false} scrollEnabled={false}
onError={e => {
onError(e.nativeEvent)
}}
onHttpError={e => {
onError(e.nativeEvent)
}}
/> />
) )
} }

View File

@ -13,8 +13,20 @@ export function CaptchaWebView({
url: string url: string
stateParam: string stateParam: string
onSuccess: (code: string) => void onSuccess: (code: string) => void
onError: () => void onError: (error: unknown) => void
}) { }) {
React.useEffect(() => {
const timeout = setTimeout(() => {
onError({
errorMessage: 'User did not complete the captcha within 30 seconds',
})
}, 30e3)
return () => {
clearTimeout(timeout)
}
}, [onError])
const onLoad = React.useCallback(() => { const onLoad = React.useCallback(() => {
// @ts-ignore web // @ts-ignore web
const frame: HTMLIFrameElement = document.getElementById( const frame: HTMLIFrameElement = document.getElementById(
@ -32,12 +44,14 @@ export function CaptchaWebView({
const code = urlp.searchParams.get('code') const code = urlp.searchParams.get('code')
if (urlp.searchParams.get('state') !== stateParam || !code) { if (urlp.searchParams.get('state') !== stateParam || !code) {
onError() onError({error: 'Invalid state or code'})
return return
} }
onSuccess(code) onSuccess(code)
} catch (e) { } catch (e: unknown) {
// We don't need to handle this // We don't actually want to record an error here, because this will happen quite a bit. We will only be able to
// get hte href of the iframe if it's on our domain, so all the hcaptcha requests will throw here, although it's
// harmless. Our other indicators of time-to-complete and back press should be more reliable in catching issues.
} }
}, [stateParam, onSuccess, onError]) }, [stateParam, onSuccess, onError])

View File

@ -5,6 +5,7 @@ import {useLingui} from '@lingui/react'
import {nanoid} from 'nanoid/non-secure' import {nanoid} from 'nanoid/non-secure'
import {createFullHandle} from '#/lib/strings/handles' import {createFullHandle} from '#/lib/strings/handles'
import {logger} from '#/logger'
import {ScreenTransition} from '#/screens/Login/ScreenTransition' import {ScreenTransition} from '#/screens/Login/ScreenTransition'
import {useSignupContext, useSubmitSignup} from '#/screens/Signup/state' import {useSignupContext, useSubmitSignup} from '#/screens/Signup/state'
import {CaptchaWebView} from '#/screens/Signup/StepCaptcha/CaptchaWebView' import {CaptchaWebView} from '#/screens/Signup/StepCaptcha/CaptchaWebView'
@ -43,12 +44,19 @@ export function StepCaptcha() {
[submit], [submit],
) )
const onError = React.useCallback(() => { const onError = React.useCallback(
(error?: unknown) => {
dispatch({ dispatch({
type: 'setError', type: 'setError',
value: _(msg`Error receiving captcha response.`), value: _(msg`Error receiving captcha response.`),
}) })
}, [_, dispatch]) logger.error('Signup Flow Error', {
registrationHandle: state.handle,
error,
})
},
[_, dispatch, state.handle],
)
return ( return (
<ScreenTransition> <ScreenTransition>

View File

@ -8,6 +8,7 @@ import {useAnalytics} from '#/lib/analytics/analytics'
import {FEEDBACK_FORM_URL} from '#/lib/constants' import {FEEDBACK_FORM_URL} from '#/lib/constants'
import {logEvent} from '#/lib/statsig/statsig' import {logEvent} from '#/lib/statsig/statsig'
import {createFullHandle} from '#/lib/strings/handles' import {createFullHandle} from '#/lib/strings/handles'
import {logger} from '#/logger'
import {useServiceQuery} from '#/state/queries/service' import {useServiceQuery} from '#/state/queries/service'
import {useAgent} from '#/state/session' import {useAgent} from '#/state/session'
import {LoggedOutLayout} from '#/view/com/util/layouts/LoggedOutLayout' import {LoggedOutLayout} from '#/view/com/util/layouts/LoggedOutLayout'
@ -119,11 +120,19 @@ export function Signup({onPressBack}: {onPressBack: () => void}) {
const onBackPress = React.useCallback(() => { const onBackPress = React.useCallback(() => {
if (state.activeStep !== SignupStep.INFO) { if (state.activeStep !== SignupStep.INFO) {
if (state.activeStep === SignupStep.CAPTCHA) {
logger.error('Signup Flow Error', {
errorMessage:
'User went back from captcha step. Possibly encountered an error.',
registrationHandle: state.handle,
})
}
dispatch({type: 'prev'}) dispatch({type: 'prev'})
} else { } else {
onPressBack() onPressBack()
} }
}, [onPressBack, state.activeStep]) }, [onPressBack, state.activeStep, state.handle])
return ( return (
<SignupContext.Provider value={{state, dispatch}}> <SignupContext.Provider value={{state, dispatch}}>

View File

@ -246,6 +246,10 @@ export function useSubmitSignup({
!verificationCode !verificationCode
) { ) {
dispatch({type: 'setStep', value: SignupStep.CAPTCHA}) dispatch({type: 'setStep', value: SignupStep.CAPTCHA})
logger.error('Signup Flow Error', {
errorMessage: 'Verification captcha code was not set.',
registrationHandle: state.handle,
})
return dispatch({ return dispatch({
type: 'setError', type: 'setError',
value: _(msg`Please complete the verification captcha.`), value: _(msg`Please complete the verification captcha.`),
@ -282,20 +286,17 @@ export function useSubmitSignup({
return return
} }
if ([400, 429].includes(e.status)) {
logger.warn('Failed to create account', {message: e})
} else {
logger.error(`Failed to create account (${e.status} status)`, {
message: e,
})
}
const error = cleanError(errMsg) const error = cleanError(errMsg)
const isHandleError = error.toLowerCase().includes('handle') const isHandleError = error.toLowerCase().includes('handle')
dispatch({type: 'setIsLoading', value: false}) dispatch({type: 'setIsLoading', value: false})
dispatch({type: 'setError', value: cleanError(errMsg)}) dispatch({type: 'setError', value: error})
dispatch({type: 'setStep', value: isHandleError ? 2 : 1}) dispatch({type: 'setStep', value: isHandleError ? 2 : 1})
logger.error('Signup Flow Error', {
errorMessage: error,
registrationHandle: state.handle,
})
} finally { } finally {
dispatch({type: 'setIsLoading', value: false}) dispatch({type: 'setIsLoading', value: false})
} }