[Statsig] Track login/logout (#3286)
* [Statsig] Track login/logout * Fix missing attributionzio/stable
parent
2e2fae378a
commit
3d8d1dd173
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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': {}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}))
|
||||
|
|
|
@ -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}`))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}`)}
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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}`)}
|
||||
|
|
Loading…
Reference in New Issue