about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSamuel Newman <mozzius@protonmail.com>2025-02-14 19:52:53 +0000
committerGitHub <noreply@github.com>2025-02-14 19:52:53 +0000
commitd793abf8cd2cf7edf2ffd4a4cd570db33b795bbf (patch)
treec6f106cedcdd3e04ecde4b6a9d3adcc5b6da7826
parent11616846caa69ee1b2eacc87e1e307b47df223a5 (diff)
downloadvoidsky-d793abf8cd2cf7edf2ffd4a4cd570db33b795bbf.tar.zst
[Instrumentation] Signin (#7742)
* first pass at instrumenting login

* round time taken
-rw-r--r--src/lib/statsig/events.ts16
-rw-r--r--src/screens/Login/LoginForm.tsx52
-rw-r--r--src/screens/Login/SetNewPasswordForm.tsx4
-rw-r--r--src/screens/Login/index.tsx33
-rw-r--r--src/view/com/auth/server-input/index.tsx6
5 files changed, 85 insertions, 26 deletions
diff --git a/src/lib/statsig/events.ts b/src/lib/statsig/events.ts
index 9a45a620e..519e3997e 100644
--- a/src/lib/statsig/events.ts
+++ b/src/lib/statsig/events.ts
@@ -51,6 +51,22 @@ export type LogEvents = {
   }
   'signup:captchaSuccess': {}
   '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': {
     selectedInterests: string[]
     selectedInterestsLength: number
diff --git a/src/screens/Login/LoginForm.tsx b/src/screens/Login/LoginForm.tsx
index 86e541b3e..134046ff3 100644
--- a/src/screens/Login/LoginForm.tsx
+++ b/src/screens/Login/LoginForm.tsx
@@ -45,6 +45,8 @@ export const LoginForm = ({
   onPressRetryConnect,
   onPressBack,
   onPressForgotPassword,
+  onAttemptSuccess,
+  onAttemptFailed,
 }: {
   error: string
   serviceUrl: string
@@ -55,6 +57,8 @@ export const LoginForm = ({
   onPressRetryConnect: () => void
   onPressBack: () => void
   onPressForgotPassword: () => void
+  onAttemptSuccess: () => void
+  onAttemptFailed: () => void
 }) => {
   const t = useTheme()
   const [isProcessing, setIsProcessing] = useState<boolean>(false)
@@ -131,6 +135,7 @@ export const LoginForm = ({
         },
         'LoginForm',
       )
+      onAttemptSuccess()
       setShowLoggedOut(false)
       setHasCheckedForStarterPack(true)
       requestNotificationsPermission('Login')
@@ -142,29 +147,32 @@ export const LoginForm = ({
         e instanceof ComAtprotoServerCreateSession.AuthFactorTokenRequiredError
       ) {
         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 {
-        logger.warn('Failed to login', {error: errMsg})
-        setError(cleanError(errMsg))
+        onAttemptFailed()
+        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))
+        }
       }
     }
   }
diff --git a/src/screens/Login/SetNewPasswordForm.tsx b/src/screens/Login/SetNewPasswordForm.tsx
index 0eb3ecce8..d78a856cb 100644
--- a/src/screens/Login/SetNewPasswordForm.tsx
+++ b/src/screens/Login/SetNewPasswordForm.tsx
@@ -4,6 +4,7 @@ import {BskyAgent} from '@atproto/api'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
+import {logEvent} from '#/lib/statsig/statsig'
 import {isNetworkError} from '#/lib/strings/errors'
 import {cleanError} from '#/lib/strings/errors'
 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.`,
         ),
       )
+      logEvent('signin:passwordResetFailure', {})
       return
     }
 
@@ -67,9 +69,11 @@ export const SetNewPasswordForm = ({
         password,
       })
       onPasswordSet()
+      logEvent('signin:passwordResetSuccess', {})
     } catch (e: any) {
       const errMsg = e.toString()
       logger.warn('Failed to set new password', {error: e})
+      logEvent('signin:passwordResetFailure', {})
       setIsProcessing(false)
       if (isNetworkError(e)) {
         setError(
diff --git a/src/screens/Login/index.tsx b/src/screens/Login/index.tsx
index b46f8d26b..8ed8d2da8 100644
--- a/src/screens/Login/index.tsx
+++ b/src/screens/Login/index.tsx
@@ -1,10 +1,11 @@
-import React from 'react'
+import React, {useRef} from 'react'
 import {KeyboardAvoidingView} from 'react-native'
 import {LayoutAnimationConfig} from 'react-native-reanimated'
 import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
 import {DEFAULT_SERVICE} from '#/lib/constants'
+import {logEvent} from '#/lib/statsig/statsig'
 import {logger} from '#/logger'
 import {useServiceQuery} from '#/state/queries/service'
 import {SessionAccount, useSession} from '#/state/session'
@@ -28,6 +29,8 @@ enum Forms {
 
 export const Login = ({onPressBack}: {onPressBack: () => void}) => {
   const {_} = useLingui()
+  const failedAttemptCountRef = useRef(0)
+  const startTimeRef = useRef(Date.now())
 
   const {accounts} = useSession()
   const {requestedAccountSwitchTo} = useLoggedOutView()
@@ -79,6 +82,7 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
       logger.warn(`Failed to fetch service description for ${serviceUrl}`, {
         error: String(serviceError),
       })
+      logEvent('signin:hostingProviderFailedResolution', {})
     } else {
       setError('')
     }
@@ -86,6 +90,27 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
 
   const onPressForgotPassword = () => {
     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
@@ -103,9 +128,11 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
           serviceDescription={serviceDescription}
           initialHandle={initialHandle}
           setError={setError}
+          onAttemptFailed={onAttemptFailed}
+          onAttemptSuccess={onAttemptSuccess}
           setServiceUrl={setServiceUrl}
           onPressBack={() =>
-            accounts.length ? gotoForm(Forms.ChooseAccount) : onPressBack()
+            accounts.length ? gotoForm(Forms.ChooseAccount) : handlePressBack()
           }
           onPressForgotPassword={onPressForgotPassword}
           onPressRetryConnect={refetchService}
@@ -118,7 +145,7 @@ export const Login = ({onPressBack}: {onPressBack: () => void}) => {
       content = (
         <ChooseAccountForm
           onSelectAccount={onSelectAccount}
-          onPressBack={onPressBack}
+          onPressBack={handlePressBack}
         />
       )
       break
diff --git a/src/view/com/auth/server-input/index.tsx b/src/view/com/auth/server-input/index.tsx
index 7c0bda45b..9fd426a9b 100644
--- a/src/view/com/auth/server-input/index.tsx
+++ b/src/view/com/auth/server-input/index.tsx
@@ -5,6 +5,7 @@ import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
 import {BSKY_SERVICE} from '#/lib/constants'
+import {logEvent} from '#/lib/statsig/statsig'
 import * as persisted from '#/state/persisted'
 import {useSession} from '#/state/session'
 import {atoms as a, useBreakpoints, useTheme} from '#/alf'
@@ -39,7 +40,10 @@ export function ServerInputDialog({
         setPreviousCustomAddress(result)
       }
     }
-  }, [onSelect])
+    logEvent('signin:hostingProviderPressed', {
+      hostingProviderDidChange: fixedOption !== BSKY_SERVICE,
+    })
+  }, [onSelect, fixedOption])
 
   return (
     <Dialog.Outer