Merge branch 'main' of github.com:bluesky-social/social-app into main

zio/stable
Paul Frazee 2024-03-19 20:24:18 -07:00
commit b5bda17812
10 changed files with 98 additions and 48 deletions

View File

@ -565,7 +565,11 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
} }
function getCurrentRouteName() { function getCurrentRouteName() {
return navigationRef.getCurrentRoute()?.name if (navigationRef.isReady()) {
return navigationRef.getCurrentRoute()?.name
} else {
return undefined
}
} }
/** /**

View File

@ -6,6 +6,7 @@ import {useSessionApi, SessionAccount} from '#/state/session'
import * as Toast from '#/view/com/util/Toast' import * as Toast from '#/view/com/util/Toast'
import {useCloseAllActiveElements} from '#/state/util' import {useCloseAllActiveElements} from '#/state/util'
import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {LogEvents} from '../statsig/statsig'
export function useAccountSwitcher() { export function useAccountSwitcher() {
const {track} = useAnalytics() const {track} = useAnalytics()
@ -14,7 +15,10 @@ export function useAccountSwitcher() {
const {requestSwitchToAccount} = useLoggedOutViewControls() const {requestSwitchToAccount} = useLoggedOutViewControls()
const onPressSwitchAccount = useCallback( const onPressSwitchAccount = useCallback(
async (account: SessionAccount) => { async (
account: SessionAccount,
logContext: LogEvents['account:loggedIn']['logContext'],
) => {
track('Settings:SwitchAccountButtonClicked') track('Settings:SwitchAccountButtonClicked')
try { try {
@ -28,7 +32,7 @@ export function useAccountSwitcher() {
// So we change the URL ourselves. The navigator will pick it up on remount. // So we change the URL ourselves. The navigator will pick it up on remount.
history.pushState(null, '', '/') history.pushState(null, '', '/')
} }
await selectAccount(account) await selectAccount(account, logContext)
setTimeout(() => { setTimeout(() => {
Toast.show(`Signed in as @${account.handle}`) Toast.show(`Signed in as @${account.handle}`)
}, 100) }, 100)

View File

@ -2,6 +2,13 @@ export type LogEvents = {
init: { init: {
initMs: number initMs: number
} }
'account:loggedIn': {
logContext: 'LoginForm' | 'SwitchAccount' | 'ChooseAccountForm' | 'Settings'
withPassword: boolean
}
'account:loggedOut': {
logContext: 'SwitchAccount' | 'Settings' | 'Deactivated'
}
'notifications:openApp': {} 'notifications:openApp': {}
'state:background': {} 'state:background': {}
'state:foreground': {} 'state:foreground': {}

View File

@ -147,7 +147,7 @@ export function Deactivated() {
variant="ghost" variant="ghost"
size="large" size="large"
label={_(msg`Log out`)} label={_(msg`Log out`)}
onPress={logout}> onPress={() => logout('Deactivated')}>
<ButtonText style={[{color: t.palette.primary_500}]}> <ButtonText style={[{color: t.palette.primary_500}]}>
<Trans>Log out</Trans> <Trans>Log out</Trans>
</ButtonText> </ButtonText>
@ -176,7 +176,7 @@ export function Deactivated() {
variant="ghost" variant="ghost"
size="large" size="large"
label={_(msg`Log out`)} label={_(msg`Log out`)}
onPress={logout}> onPress={() => logout('Deactivated')}>
<ButtonText style={[{color: t.palette.primary_500}]}> <ButtonText style={[{color: t.palette.primary_500}]}>
<Trans>Log out</Trans> <Trans>Log out</Trans>
</ButtonText> </ButtonText>

View File

@ -20,6 +20,7 @@ import {useCloseAllActiveElements} from '#/state/util'
import {track} from '#/lib/analytics/analytics' import {track} from '#/lib/analytics/analytics'
import {hasProp} from '#/lib/type-guards' import {hasProp} from '#/lib/type-guards'
import {readLabelers} from './agent-config' import {readLabelers} from './agent-config'
import {logEvent, LogEvents} from '#/lib/statsig/statsig'
let __globalAgent: BskyAgent = PUBLIC_BSKY_AGENT let __globalAgent: BskyAgent = PUBLIC_BSKY_AGENT
@ -54,17 +55,22 @@ export type ApiContext = {
verificationPhone?: string verificationPhone?: string
verificationCode?: string verificationCode?: string
}) => Promise<void> }) => Promise<void>
login: (props: { login: (
service: string props: {
identifier: string service: string
password: string identifier: string
}) => Promise<void> password: string
},
logContext: LogEvents['account:loggedIn']['logContext'],
) => Promise<void>
/** /**
* A full logout. Clears the `currentAccount` from session, AND removes * A full logout. Clears the `currentAccount` from session, AND removes
* access tokens from all accounts, so that returning as any user will * access tokens from all accounts, so that returning as any user will
* require a full login. * 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 * A partial logout. Clears the `currentAccount` from session, but DOES NOT
* clear access tokens from accounts, allowing the user to return to their * clear access tokens from accounts, allowing the user to return to their
@ -76,7 +82,10 @@ export type ApiContext = {
initSession: (account: SessionAccount) => Promise<void> initSession: (account: SessionAccount) => Promise<void>
resumeSession: (account?: SessionAccount) => Promise<void> resumeSession: (account?: SessionAccount) => Promise<void>
removeAccount: (account: SessionAccount) => void removeAccount: (account: SessionAccount) => void
selectAccount: (account: SessionAccount) => Promise<void> selectAccount: (
account: SessionAccount,
logContext: LogEvents['account:loggedIn']['logContext'],
) => Promise<void>
updateCurrentAccount: ( updateCurrentAccount: (
account: Partial< account: Partial<
Pick<SessionAccount, 'handle' | 'email' | 'emailConfirmed'> Pick<SessionAccount, 'handle' | 'email' | 'emailConfirmed'>
@ -286,7 +295,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
) )
const login = React.useCallback<ApiContext['login']>( const login = React.useCallback<ApiContext['login']>(
async ({service, identifier, password}) => { async ({service, identifier, password}, logContext) => {
logger.debug(`session: login`, {}, logger.DebugContext.session) logger.debug(`session: login`, {}, logger.DebugContext.session)
const agent = new BskyAgent({service}) const agent = new BskyAgent({service})
@ -329,24 +338,29 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
logger.debug(`session: logged in`, {}, logger.DebugContext.session) logger.debug(`session: logged in`, {}, logger.DebugContext.session)
track('Sign In', {resumedSession: false}) track('Sign In', {resumedSession: false})
logEvent('account:loggedIn', {logContext, withPassword: true})
}, },
[upsertAccount, queryClient, clearCurrentAccount], [upsertAccount, queryClient, clearCurrentAccount],
) )
const logout = React.useCallback<ApiContext['logout']>(async () => { const logout = React.useCallback<ApiContext['logout']>(
logger.debug(`session: logout`) async logContext => {
clearCurrentAccount() logger.debug(`session: logout`)
setStateAndPersist(s => { clearCurrentAccount()
return { setStateAndPersist(s => {
...s, return {
accounts: s.accounts.map(a => ({ ...s,
...a, accounts: s.accounts.map(a => ({
refreshJwt: undefined, ...a,
accessJwt: undefined, refreshJwt: undefined,
})), accessJwt: undefined,
} })),
}) }
}, [clearCurrentAccount, setStateAndPersist]) })
logEvent('account:loggedOut', {logContext})
},
[clearCurrentAccount, setStateAndPersist],
)
const initSession = React.useCallback<ApiContext['initSession']>( const initSession = React.useCallback<ApiContext['initSession']>(
async account => { async account => {
@ -540,11 +554,12 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
) )
const selectAccount = React.useCallback<ApiContext['selectAccount']>( const selectAccount = React.useCallback<ApiContext['selectAccount']>(
async account => { async (account, logContext) => {
setState(s => ({...s, isSwitchingAccounts: true})) setState(s => ({...s, isSwitchingAccounts: true}))
try { try {
await initSession(account) await initSession(account)
setState(s => ({...s, isSwitchingAccounts: false})) setState(s => ({...s, isSwitchingAccounts: false}))
logEvent('account:loggedIn', {logContext, withPassword: false})
} catch (e) { } catch (e) {
// reset this in case of error // reset this in case of error
setState(s => ({...s, isSwitchingAccounts: false})) setState(s => ({...s, isSwitchingAccounts: false}))

View File

@ -16,6 +16,7 @@ import {useSession, useSessionApi, SessionAccount} from '#/state/session'
import {useProfileQuery} from '#/state/queries/profile' import {useProfileQuery} from '#/state/queries/profile'
import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import * as Toast from '#/view/com/util/Toast' import * as Toast from '#/view/com/util/Toast'
import {logEvent} from '#/lib/statsig/statsig'
function AccountItem({ function AccountItem({
account, account,
@ -102,6 +103,10 @@ export const ChooseAccountForm = ({
Toast.show(_(msg`Already signed in as @${account.handle}`)) Toast.show(_(msg`Already signed in as @${account.handle}`))
} else { } else {
await initSession(account) await initSession(account)
logEvent('account:loggedIn', {
logContext: 'ChooseAccountForm',
withPassword: false,
})
track('Sign In', {resumedSession: true}) track('Sign In', {resumedSession: true})
setTimeout(() => { setTimeout(() => {
Toast.show(_(msg`Signed in as @${account.handle}`)) Toast.show(_(msg`Signed in as @${account.handle}`))

View File

@ -98,11 +98,14 @@ export const LoginForm = ({
} }
// TODO remove double login // TODO remove double login
await login({ await login(
service: serviceUrl, {
identifier: fullIdent, service: serviceUrl,
password, identifier: fullIdent,
}) password,
},
'LoginForm',
)
} catch (e: any) { } catch (e: any) {
const errMsg = e.toString() const errMsg = e.toString()
setIsProcessing(false) setIsProcessing(false)

View File

@ -39,7 +39,7 @@ function SwitchAccountCard({account}: {account: SessionAccount}) {
track('Settings:SignOutButtonClicked') track('Settings:SignOutButtonClicked')
closeAllActiveElements() closeAllActiveElements()
// needs to be in timeout or the modal re-opens // needs to be in timeout or the modal re-opens
setTimeout(() => logout(), 0) setTimeout(() => logout('SwitchAccount'), 0)
}, [track, logout, closeAllActiveElements]) }, [track, logout, closeAllActiveElements])
const contents = ( const contents = (
@ -95,7 +95,9 @@ function SwitchAccountCard({account}: {account: SessionAccount}) {
key={account.did} key={account.did}
style={[isSwitchingAccounts && styles.dimmed]} style={[isSwitchingAccounts && styles.dimmed]}
onPress={ onPress={
isSwitchingAccounts ? undefined : () => onPressSwitchAccount(account) isSwitchingAccounts
? undefined
: () => onPressSwitchAccount(account, 'SwitchAccount')
} }
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Switch to ${account.handle}`)} accessibilityLabel={_(msg`Switch to ${account.handle}`)}

View File

@ -22,18 +22,24 @@ export function TestCtrls() {
const {mutate: setFeedViewPref} = useSetFeedViewPreferencesMutation() const {mutate: setFeedViewPref} = useSetFeedViewPreferencesMutation()
const {setShowLoggedOut} = useLoggedOutViewControls() const {setShowLoggedOut} = useLoggedOutViewControls()
const onPressSignInAlice = async () => { const onPressSignInAlice = async () => {
await login({ await login(
service: 'http://localhost:3000', {
identifier: 'alice.test', service: 'http://localhost:3000',
password: 'hunter2', identifier: 'alice.test',
}) password: 'hunter2',
},
'LoginForm',
)
} }
const onPressSignInBob = async () => { const onPressSignInBob = async () => {
await login({ await login(
service: 'http://localhost:3000', {
identifier: 'bob.test', service: 'http://localhost:3000',
password: 'hunter2', identifier: 'bob.test',
}) password: 'hunter2',
},
'LoginForm',
)
} }
return ( return (
<View style={{position: 'absolute', top: 100, right: 0, zIndex: 100}}> <View style={{position: 'absolute', top: 100, right: 0, zIndex: 100}}>
@ -51,7 +57,7 @@ export function TestCtrls() {
/> />
<Pressable <Pressable
testID="e2eSignOut" testID="e2eSignOut"
onPress={() => logout()} onPress={() => logout('Settings')}
accessibilityRole="button" accessibilityRole="button"
style={BTN} style={BTN}
/> />

View File

@ -100,7 +100,9 @@ function SettingsAccountCard({account}: {account: SessionAccount}) {
{isCurrentAccount ? ( {isCurrentAccount ? (
<TouchableOpacity <TouchableOpacity
testID="signOutBtn" testID="signOutBtn"
onPress={logout} onPress={() => {
logout('Settings')
}}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Sign out`)} accessibilityLabel={_(msg`Sign out`)}
accessibilityHint={`Signs ${profile?.displayName} out of Bluesky`}> accessibilityHint={`Signs ${profile?.displayName} out of Bluesky`}>
@ -129,7 +131,9 @@ function SettingsAccountCard({account}: {account: SessionAccount}) {
testID={`switchToAccountBtn-${account.handle}`} testID={`switchToAccountBtn-${account.handle}`}
key={account.did} key={account.did}
onPress={ onPress={
isSwitchingAccounts ? undefined : () => onPressSwitchAccount(account) isSwitchingAccounts
? undefined
: () => onPressSwitchAccount(account, 'Settings')
} }
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Switch to ${account.handle}`)} accessibilityLabel={_(msg`Switch to ${account.handle}`)}