about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/Navigation.tsx6
-rw-r--r--src/lib/hooks/useAccountSwitcher.ts8
-rw-r--r--src/lib/statsig/events.ts7
-rw-r--r--src/screens/Deactivated.tsx4
-rw-r--r--src/state/session/index.tsx61
-rw-r--r--src/view/com/auth/login/ChooseAccountForm.tsx5
-rw-r--r--src/view/com/auth/login/LoginForm.tsx13
-rw-r--r--src/view/com/modals/SwitchAccount.tsx6
-rw-r--r--src/view/com/testing/TestCtrls.e2e.tsx28
-rw-r--r--src/view/screens/Settings/index.tsx8
10 files changed, 98 insertions, 48 deletions
diff --git a/src/Navigation.tsx b/src/Navigation.tsx
index 3d6a15c4e..83aede722 100644
--- a/src/Navigation.tsx
+++ b/src/Navigation.tsx
@@ -565,7 +565,11 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
 }
 
 function getCurrentRouteName() {
-  return navigationRef.getCurrentRoute()?.name
+  if (navigationRef.isReady()) {
+    return navigationRef.getCurrentRoute()?.name
+  } else {
+    return undefined
+  }
 }
 
 /**
diff --git a/src/lib/hooks/useAccountSwitcher.ts b/src/lib/hooks/useAccountSwitcher.ts
index 74b5674d5..eb1685a0a 100644
--- a/src/lib/hooks/useAccountSwitcher.ts
+++ b/src/lib/hooks/useAccountSwitcher.ts
@@ -6,6 +6,7 @@ import {useSessionApi, SessionAccount} from '#/state/session'
 import * as Toast from '#/view/com/util/Toast'
 import {useCloseAllActiveElements} from '#/state/util'
 import {useLoggedOutViewControls} from '#/state/shell/logged-out'
+import {LogEvents} from '../statsig/statsig'
 
 export function useAccountSwitcher() {
   const {track} = useAnalytics()
@@ -14,7 +15,10 @@ export function useAccountSwitcher() {
   const {requestSwitchToAccount} = useLoggedOutViewControls()
 
   const onPressSwitchAccount = useCallback(
-    async (account: SessionAccount) => {
+    async (
+      account: SessionAccount,
+      logContext: LogEvents['account:loggedIn']['logContext'],
+    ) => {
       track('Settings:SwitchAccountButtonClicked')
 
       try {
@@ -28,7 +32,7 @@ export function useAccountSwitcher() {
             // So we change the URL ourselves. The navigator will pick it up on remount.
             history.pushState(null, '', '/')
           }
-          await selectAccount(account)
+          await selectAccount(account, logContext)
           setTimeout(() => {
             Toast.show(`Signed in as @${account.handle}`)
           }, 100)
diff --git a/src/lib/statsig/events.ts b/src/lib/statsig/events.ts
index 420c58ed2..f57c8d416 100644
--- a/src/lib/statsig/events.ts
+++ b/src/lib/statsig/events.ts
@@ -2,6 +2,13 @@ export type LogEvents = {
   init: {
     initMs: number
   }
+  'account:loggedIn': {
+    logContext: 'LoginForm' | 'SwitchAccount' | 'ChooseAccountForm' | 'Settings'
+    withPassword: boolean
+  }
+  'account:loggedOut': {
+    logContext: 'SwitchAccount' | 'Settings' | 'Deactivated'
+  }
   'notifications:openApp': {}
   'state:background': {}
   'state:foreground': {}
diff --git a/src/screens/Deactivated.tsx b/src/screens/Deactivated.tsx
index f4c201475..7e87973cb 100644
--- a/src/screens/Deactivated.tsx
+++ b/src/screens/Deactivated.tsx
@@ -147,7 +147,7 @@ export function Deactivated() {
                   variant="ghost"
                   size="large"
                   label={_(msg`Log out`)}
-                  onPress={logout}>
+                  onPress={() => logout('Deactivated')}>
                   <ButtonText style={[{color: t.palette.primary_500}]}>
                     <Trans>Log out</Trans>
                   </ButtonText>
@@ -176,7 +176,7 @@ export function Deactivated() {
               variant="ghost"
               size="large"
               label={_(msg`Log out`)}
-              onPress={logout}>
+              onPress={() => logout('Deactivated')}>
               <ButtonText style={[{color: t.palette.primary_500}]}>
                 <Trans>Log out</Trans>
               </ButtonText>
diff --git a/src/state/session/index.tsx b/src/state/session/index.tsx
index 6b1474839..b6748bfad 100644
--- a/src/state/session/index.tsx
+++ b/src/state/session/index.tsx
@@ -20,6 +20,7 @@ import {useCloseAllActiveElements} from '#/state/util'
 import {track} from '#/lib/analytics/analytics'
 import {hasProp} from '#/lib/type-guards'
 import {readLabelers} from './agent-config'
+import {logEvent, LogEvents} from '#/lib/statsig/statsig'
 
 let __globalAgent: BskyAgent = PUBLIC_BSKY_AGENT
 
@@ -54,17 +55,22 @@ export type ApiContext = {
     verificationPhone?: string
     verificationCode?: string
   }) => Promise<void>
-  login: (props: {
-    service: string
-    identifier: string
-    password: string
-  }) => Promise<void>
+  login: (
+    props: {
+      service: string
+      identifier: string
+      password: string
+    },
+    logContext: LogEvents['account:loggedIn']['logContext'],
+  ) => Promise<void>
   /**
    * A full logout. Clears the `currentAccount` from session, AND removes
    * access tokens from all accounts, so that returning as any user will
    * require a full login.
    */
-  logout: () => Promise<void>
+  logout: (
+    logContext: LogEvents['account:loggedOut']['logContext'],
+  ) => Promise<void>
   /**
    * A partial logout. Clears the `currentAccount` from session, but DOES NOT
    * clear access tokens from accounts, allowing the user to return to their
@@ -76,7 +82,10 @@ export type ApiContext = {
   initSession: (account: SessionAccount) => Promise<void>
   resumeSession: (account?: SessionAccount) => Promise<void>
   removeAccount: (account: SessionAccount) => void
-  selectAccount: (account: SessionAccount) => Promise<void>
+  selectAccount: (
+    account: SessionAccount,
+    logContext: LogEvents['account:loggedIn']['logContext'],
+  ) => Promise<void>
   updateCurrentAccount: (
     account: Partial<
       Pick<SessionAccount, 'handle' | 'email' | 'emailConfirmed'>
@@ -286,7 +295,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
   )
 
   const login = React.useCallback<ApiContext['login']>(
-    async ({service, identifier, password}) => {
+    async ({service, identifier, password}, logContext) => {
       logger.debug(`session: login`, {}, logger.DebugContext.session)
 
       const agent = new BskyAgent({service})
@@ -329,24 +338,29 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
       logger.debug(`session: logged in`, {}, logger.DebugContext.session)
 
       track('Sign In', {resumedSession: false})
+      logEvent('account:loggedIn', {logContext, withPassword: true})
     },
     [upsertAccount, queryClient, clearCurrentAccount],
   )
 
-  const logout = React.useCallback<ApiContext['logout']>(async () => {
-    logger.debug(`session: logout`)
-    clearCurrentAccount()
-    setStateAndPersist(s => {
-      return {
-        ...s,
-        accounts: s.accounts.map(a => ({
-          ...a,
-          refreshJwt: undefined,
-          accessJwt: undefined,
-        })),
-      }
-    })
-  }, [clearCurrentAccount, setStateAndPersist])
+  const logout = React.useCallback<ApiContext['logout']>(
+    async logContext => {
+      logger.debug(`session: logout`)
+      clearCurrentAccount()
+      setStateAndPersist(s => {
+        return {
+          ...s,
+          accounts: s.accounts.map(a => ({
+            ...a,
+            refreshJwt: undefined,
+            accessJwt: undefined,
+          })),
+        }
+      })
+      logEvent('account:loggedOut', {logContext})
+    },
+    [clearCurrentAccount, setStateAndPersist],
+  )
 
   const initSession = React.useCallback<ApiContext['initSession']>(
     async account => {
@@ -540,11 +554,12 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
   )
 
   const selectAccount = React.useCallback<ApiContext['selectAccount']>(
-    async account => {
+    async (account, logContext) => {
       setState(s => ({...s, isSwitchingAccounts: true}))
       try {
         await initSession(account)
         setState(s => ({...s, isSwitchingAccounts: false}))
+        logEvent('account:loggedIn', {logContext, withPassword: false})
       } catch (e) {
         // reset this in case of error
         setState(s => ({...s, isSwitchingAccounts: false}))
diff --git a/src/view/com/auth/login/ChooseAccountForm.tsx b/src/view/com/auth/login/ChooseAccountForm.tsx
index d3b075fdb..e754c8483 100644
--- a/src/view/com/auth/login/ChooseAccountForm.tsx
+++ b/src/view/com/auth/login/ChooseAccountForm.tsx
@@ -16,6 +16,7 @@ import {useSession, useSessionApi, SessionAccount} from '#/state/session'
 import {useProfileQuery} from '#/state/queries/profile'
 import {useLoggedOutViewControls} from '#/state/shell/logged-out'
 import * as Toast from '#/view/com/util/Toast'
+import {logEvent} from '#/lib/statsig/statsig'
 
 function AccountItem({
   account,
@@ -102,6 +103,10 @@ export const ChooseAccountForm = ({
           Toast.show(_(msg`Already signed in as @${account.handle}`))
         } else {
           await initSession(account)
+          logEvent('account:loggedIn', {
+            logContext: 'ChooseAccountForm',
+            withPassword: false,
+          })
           track('Sign In', {resumedSession: true})
           setTimeout(() => {
             Toast.show(_(msg`Signed in as @${account.handle}`))
diff --git a/src/view/com/auth/login/LoginForm.tsx b/src/view/com/auth/login/LoginForm.tsx
index 3202d69c5..92f495575 100644
--- a/src/view/com/auth/login/LoginForm.tsx
+++ b/src/view/com/auth/login/LoginForm.tsx
@@ -98,11 +98,14 @@ export const LoginForm = ({
       }
 
       // TODO remove double login
-      await login({
-        service: serviceUrl,
-        identifier: fullIdent,
-        password,
-      })
+      await login(
+        {
+          service: serviceUrl,
+          identifier: fullIdent,
+          password,
+        },
+        'LoginForm',
+      )
     } catch (e: any) {
       const errMsg = e.toString()
       setIsProcessing(false)
diff --git a/src/view/com/modals/SwitchAccount.tsx b/src/view/com/modals/SwitchAccount.tsx
index 0658805bd..892b07c9a 100644
--- a/src/view/com/modals/SwitchAccount.tsx
+++ b/src/view/com/modals/SwitchAccount.tsx
@@ -39,7 +39,7 @@ function SwitchAccountCard({account}: {account: SessionAccount}) {
     track('Settings:SignOutButtonClicked')
     closeAllActiveElements()
     // needs to be in timeout or the modal re-opens
-    setTimeout(() => logout(), 0)
+    setTimeout(() => logout('SwitchAccount'), 0)
   }, [track, logout, closeAllActiveElements])
 
   const contents = (
@@ -95,7 +95,9 @@ function SwitchAccountCard({account}: {account: SessionAccount}) {
       key={account.did}
       style={[isSwitchingAccounts && styles.dimmed]}
       onPress={
-        isSwitchingAccounts ? undefined : () => onPressSwitchAccount(account)
+        isSwitchingAccounts
+          ? undefined
+          : () => onPressSwitchAccount(account, 'SwitchAccount')
       }
       accessibilityRole="button"
       accessibilityLabel={_(msg`Switch to ${account.handle}`)}
diff --git a/src/view/com/testing/TestCtrls.e2e.tsx b/src/view/com/testing/TestCtrls.e2e.tsx
index e1e899488..1eb99c4f5 100644
--- a/src/view/com/testing/TestCtrls.e2e.tsx
+++ b/src/view/com/testing/TestCtrls.e2e.tsx
@@ -22,18 +22,24 @@ export function TestCtrls() {
   const {mutate: setFeedViewPref} = useSetFeedViewPreferencesMutation()
   const {setShowLoggedOut} = useLoggedOutViewControls()
   const onPressSignInAlice = async () => {
-    await login({
-      service: 'http://localhost:3000',
-      identifier: 'alice.test',
-      password: 'hunter2',
-    })
+    await login(
+      {
+        service: 'http://localhost:3000',
+        identifier: 'alice.test',
+        password: 'hunter2',
+      },
+      'LoginForm',
+    )
   }
   const onPressSignInBob = async () => {
-    await login({
-      service: 'http://localhost:3000',
-      identifier: 'bob.test',
-      password: 'hunter2',
-    })
+    await login(
+      {
+        service: 'http://localhost:3000',
+        identifier: 'bob.test',
+        password: 'hunter2',
+      },
+      'LoginForm',
+    )
   }
   return (
     <View style={{position: 'absolute', top: 100, right: 0, zIndex: 100}}>
@@ -51,7 +57,7 @@ export function TestCtrls() {
       />
       <Pressable
         testID="e2eSignOut"
-        onPress={() => logout()}
+        onPress={() => logout('Settings')}
         accessibilityRole="button"
         style={BTN}
       />
diff --git a/src/view/screens/Settings/index.tsx b/src/view/screens/Settings/index.tsx
index 7e808f910..3967678b4 100644
--- a/src/view/screens/Settings/index.tsx
+++ b/src/view/screens/Settings/index.tsx
@@ -100,7 +100,9 @@ function SettingsAccountCard({account}: {account: SessionAccount}) {
       {isCurrentAccount ? (
         <TouchableOpacity
           testID="signOutBtn"
-          onPress={logout}
+          onPress={() => {
+            logout('Settings')
+          }}
           accessibilityRole="button"
           accessibilityLabel={_(msg`Sign out`)}
           accessibilityHint={`Signs ${profile?.displayName} out of Bluesky`}>
@@ -129,7 +131,9 @@ function SettingsAccountCard({account}: {account: SessionAccount}) {
       testID={`switchToAccountBtn-${account.handle}`}
       key={account.did}
       onPress={
-        isSwitchingAccounts ? undefined : () => onPressSwitchAccount(account)
+        isSwitchingAccounts
+          ? undefined
+          : () => onPressSwitchAccount(account, 'Settings')
       }
       accessibilityRole="button"
       accessibilityLabel={_(msg`Switch to ${account.handle}`)}