[Instrumentation] Signin (#7742)
* first pass at instrumenting login * round time taken
This commit is contained in:
@@ -51,6 +51,22 @@ export type LogEvents = {
|
|||||||
}
|
}
|
||||||
'signup:captchaSuccess': {}
|
'signup:captchaSuccess': {}
|
||||||
'signup:captchaFailure': {}
|
'signup:captchaFailure': {}
|
||||||
|
'signin:hostingProviderPressed': {
|
||||||
|
hostingProviderDidChange: boolean
|
||||||
|
}
|
||||||
|
'signin:hostingProviderFailedResolution': {}
|
||||||
|
'signin:success': {
|
||||||
|
failedAttemptsCount: number
|
||||||
|
isUsingCustomProvider: boolean
|
||||||
|
timeTakenSeconds: number
|
||||||
|
}
|
||||||
|
'signin:backPressed': {
|
||||||
|
failedAttemptsCount: number
|
||||||
|
}
|
||||||
|
'signin:forgotPasswordPressed': {}
|
||||||
|
'signin:passwordReset': {}
|
||||||
|
'signin:passwordResetSuccess': {}
|
||||||
|
'signin:passwordResetFailure': {}
|
||||||
'onboarding:interests:nextPressed': {
|
'onboarding:interests:nextPressed': {
|
||||||
selectedInterests: string[]
|
selectedInterests: string[]
|
||||||
selectedInterestsLength: number
|
selectedInterestsLength: number
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ export const LoginForm = ({
|
|||||||
onPressRetryConnect,
|
onPressRetryConnect,
|
||||||
onPressBack,
|
onPressBack,
|
||||||
onPressForgotPassword,
|
onPressForgotPassword,
|
||||||
|
onAttemptSuccess,
|
||||||
|
onAttemptFailed,
|
||||||
}: {
|
}: {
|
||||||
error: string
|
error: string
|
||||||
serviceUrl: string
|
serviceUrl: string
|
||||||
@@ -55,6 +57,8 @@ export const LoginForm = ({
|
|||||||
onPressRetryConnect: () => void
|
onPressRetryConnect: () => void
|
||||||
onPressBack: () => void
|
onPressBack: () => void
|
||||||
onPressForgotPassword: () => void
|
onPressForgotPassword: () => void
|
||||||
|
onAttemptSuccess: () => void
|
||||||
|
onAttemptFailed: () => void
|
||||||
}) => {
|
}) => {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const [isProcessing, setIsProcessing] = useState<boolean>(false)
|
const [isProcessing, setIsProcessing] = useState<boolean>(false)
|
||||||
@@ -131,6 +135,7 @@ export const LoginForm = ({
|
|||||||
},
|
},
|
||||||
'LoginForm',
|
'LoginForm',
|
||||||
)
|
)
|
||||||
|
onAttemptSuccess()
|
||||||
setShowLoggedOut(false)
|
setShowLoggedOut(false)
|
||||||
setHasCheckedForStarterPack(true)
|
setHasCheckedForStarterPack(true)
|
||||||
requestNotificationsPermission('Login')
|
requestNotificationsPermission('Login')
|
||||||
@@ -142,29 +147,32 @@ export const LoginForm = ({
|
|||||||
e instanceof ComAtprotoServerCreateSession.AuthFactorTokenRequiredError
|
e instanceof ComAtprotoServerCreateSession.AuthFactorTokenRequiredError
|
||||||
) {
|
) {
|
||||||
setIsAuthFactorTokenNeeded(true)
|
setIsAuthFactorTokenNeeded(true)
|
||||||
} else if (errMsg.includes('Token is invalid')) {
|
|
||||||
logger.debug('Failed to login due to invalid 2fa token', {
|
|
||||||
error: errMsg,
|
|
||||||
})
|
|
||||||
setError(_(msg`Invalid 2FA confirmation code.`))
|
|
||||||
} else if (
|
|
||||||
errMsg.includes('Authentication Required') ||
|
|
||||||
errMsg.includes('Invalid identifier or password')
|
|
||||||
) {
|
|
||||||
logger.debug('Failed to login due to invalid credentials', {
|
|
||||||
error: errMsg,
|
|
||||||
})
|
|
||||||
setError(_(msg`Incorrect username or password`))
|
|
||||||
} else if (isNetworkError(e)) {
|
|
||||||
logger.warn('Failed to login due to network error', {error: errMsg})
|
|
||||||
setError(
|
|
||||||
_(
|
|
||||||
msg`Unable to contact your service. Please check your Internet connection.`,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
logger.warn('Failed to login', {error: errMsg})
|
onAttemptFailed()
|
||||||
setError(cleanError(errMsg))
|
if (errMsg.includes('Token is invalid')) {
|
||||||
|
logger.debug('Failed to login due to invalid 2fa token', {
|
||||||
|
error: errMsg,
|
||||||
|
})
|
||||||
|
setError(_(msg`Invalid 2FA confirmation code.`))
|
||||||
|
} else if (
|
||||||
|
errMsg.includes('Authentication Required') ||
|
||||||
|
errMsg.includes('Invalid identifier or password')
|
||||||
|
) {
|
||||||
|
logger.debug('Failed to login due to invalid credentials', {
|
||||||
|
error: errMsg,
|
||||||
|
})
|
||||||
|
setError(_(msg`Incorrect username or password`))
|
||||||
|
} else if (isNetworkError(e)) {
|
||||||
|
logger.warn('Failed to login due to network error', {error: errMsg})
|
||||||
|
setError(
|
||||||
|
_(
|
||||||
|
msg`Unable to contact your service. Please check your Internet connection.`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
logger.warn('Failed to login', {error: errMsg})
|
||||||
|
setError(cleanError(errMsg))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {BskyAgent} from '@atproto/api'
|
|||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
|
import {logEvent} from '#/lib/statsig/statsig'
|
||||||
import {isNetworkError} from '#/lib/strings/errors'
|
import {isNetworkError} from '#/lib/strings/errors'
|
||||||
import {cleanError} from '#/lib/strings/errors'
|
import {cleanError} from '#/lib/strings/errors'
|
||||||
import {checkAndFormatResetCode} from '#/lib/strings/password'
|
import {checkAndFormatResetCode} from '#/lib/strings/password'
|
||||||
@@ -48,6 +49,7 @@ export const SetNewPasswordForm = ({
|
|||||||
msg`You have entered an invalid code. It should look like XXXXX-XXXXX.`,
|
msg`You have entered an invalid code. It should look like XXXXX-XXXXX.`,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
logEvent('signin:passwordResetFailure', {})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,9 +69,11 @@ export const SetNewPasswordForm = ({
|
|||||||
password,
|
password,
|
||||||
})
|
})
|
||||||
onPasswordSet()
|
onPasswordSet()
|
||||||
|
logEvent('signin:passwordResetSuccess', {})
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
const errMsg = e.toString()
|
const errMsg = e.toString()
|
||||||
logger.warn('Failed to set new password', {error: e})
|
logger.warn('Failed to set new password', {error: e})
|
||||||
|
logEvent('signin:passwordResetFailure', {})
|
||||||
setIsProcessing(false)
|
setIsProcessing(false)
|
||||||
if (isNetworkError(e)) {
|
if (isNetworkError(e)) {
|
||||||
setError(
|
setError(
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import React from 'react'
|
import React, {useRef} from 'react'
|
||||||
import {KeyboardAvoidingView} from 'react-native'
|
import {KeyboardAvoidingView} from 'react-native'
|
||||||
import {LayoutAnimationConfig} from 'react-native-reanimated'
|
import {LayoutAnimationConfig} from 'react-native-reanimated'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {DEFAULT_SERVICE} from '#/lib/constants'
|
import {DEFAULT_SERVICE} from '#/lib/constants'
|
||||||
|
import {logEvent} from '#/lib/statsig/statsig'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {useServiceQuery} from '#/state/queries/service'
|
import {useServiceQuery} from '#/state/queries/service'
|
||||||
import {SessionAccount, useSession} from '#/state/session'
|
import {SessionAccount, useSession} from '#/state/session'
|
||||||
@@ -28,6 +29,8 @@ enum Forms {
|
|||||||
|
|
||||||
export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
|
const failedAttemptCountRef = useRef(0)
|
||||||
|
const startTimeRef = useRef(Date.now())
|
||||||
|
|
||||||
const {accounts} = useSession()
|
const {accounts} = useSession()
|
||||||
const {requestedAccountSwitchTo} = useLoggedOutView()
|
const {requestedAccountSwitchTo} = useLoggedOutView()
|
||||||
@@ -79,6 +82,7 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
|||||||
logger.warn(`Failed to fetch service description for ${serviceUrl}`, {
|
logger.warn(`Failed to fetch service description for ${serviceUrl}`, {
|
||||||
error: String(serviceError),
|
error: String(serviceError),
|
||||||
})
|
})
|
||||||
|
logEvent('signin:hostingProviderFailedResolution', {})
|
||||||
} else {
|
} else {
|
||||||
setError('')
|
setError('')
|
||||||
}
|
}
|
||||||
@@ -86,6 +90,27 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
|||||||
|
|
||||||
const onPressForgotPassword = () => {
|
const onPressForgotPassword = () => {
|
||||||
setCurrentForm(Forms.ForgotPassword)
|
setCurrentForm(Forms.ForgotPassword)
|
||||||
|
logEvent('signin:forgotPasswordPressed', {})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePressBack = () => {
|
||||||
|
onPressBack()
|
||||||
|
logEvent('signin:backPressed', {
|
||||||
|
failedAttemptsCount: failedAttemptCountRef.current,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onAttemptSuccess = () => {
|
||||||
|
logEvent('signin:success', {
|
||||||
|
isUsingCustomProvider: serviceUrl !== DEFAULT_SERVICE,
|
||||||
|
timeTakenSeconds: Math.round((Date.now() - startTimeRef.current) / 1000),
|
||||||
|
failedAttemptsCount: failedAttemptCountRef.current,
|
||||||
|
})
|
||||||
|
setCurrentForm(Forms.Login)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onAttemptFailed = () => {
|
||||||
|
failedAttemptCountRef.current += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = null
|
let content = null
|
||||||
@@ -103,9 +128,11 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
|||||||
serviceDescription={serviceDescription}
|
serviceDescription={serviceDescription}
|
||||||
initialHandle={initialHandle}
|
initialHandle={initialHandle}
|
||||||
setError={setError}
|
setError={setError}
|
||||||
|
onAttemptFailed={onAttemptFailed}
|
||||||
|
onAttemptSuccess={onAttemptSuccess}
|
||||||
setServiceUrl={setServiceUrl}
|
setServiceUrl={setServiceUrl}
|
||||||
onPressBack={() =>
|
onPressBack={() =>
|
||||||
accounts.length ? gotoForm(Forms.ChooseAccount) : onPressBack()
|
accounts.length ? gotoForm(Forms.ChooseAccount) : handlePressBack()
|
||||||
}
|
}
|
||||||
onPressForgotPassword={onPressForgotPassword}
|
onPressForgotPassword={onPressForgotPassword}
|
||||||
onPressRetryConnect={refetchService}
|
onPressRetryConnect={refetchService}
|
||||||
@@ -118,7 +145,7 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
|
|||||||
content = (
|
content = (
|
||||||
<ChooseAccountForm
|
<ChooseAccountForm
|
||||||
onSelectAccount={onSelectAccount}
|
onSelectAccount={onSelectAccount}
|
||||||
onPressBack={onPressBack}
|
onPressBack={handlePressBack}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {msg, Trans} from '@lingui/macro'
|
|||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|
||||||
import {BSKY_SERVICE} from '#/lib/constants'
|
import {BSKY_SERVICE} from '#/lib/constants'
|
||||||
|
import {logEvent} from '#/lib/statsig/statsig'
|
||||||
import * as persisted from '#/state/persisted'
|
import * as persisted from '#/state/persisted'
|
||||||
import {useSession} from '#/state/session'
|
import {useSession} from '#/state/session'
|
||||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||||
@@ -39,7 +40,10 @@ export function ServerInputDialog({
|
|||||||
setPreviousCustomAddress(result)
|
setPreviousCustomAddress(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [onSelect])
|
logEvent('signin:hostingProviderPressed', {
|
||||||
|
hostingProviderDidChange: fixedOption !== BSKY_SERVICE,
|
||||||
|
})
|
||||||
|
}, [onSelect, fixedOption])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog.Outer
|
<Dialog.Outer
|
||||||
|
|||||||
Reference in New Issue
Block a user