Merge branch 'main' of github.com:bluesky-social/social-app into main
commit
b5bda17812
|
@ -565,7 +565,11 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCurrentRouteName() {
|
function getCurrentRouteName() {
|
||||||
|
if (navigationRef.isReady()) {
|
||||||
return navigationRef.getCurrentRoute()?.name
|
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 * 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)
|
||||||
|
|
|
@ -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': {}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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: (
|
||||||
|
props: {
|
||||||
service: string
|
service: string
|
||||||
identifier: string
|
identifier: string
|
||||||
password: string
|
password: string
|
||||||
}) => Promise<void>
|
},
|
||||||
|
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,11 +338,13 @@ 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']>(
|
||||||
|
async logContext => {
|
||||||
logger.debug(`session: logout`)
|
logger.debug(`session: logout`)
|
||||||
clearCurrentAccount()
|
clearCurrentAccount()
|
||||||
setStateAndPersist(s => {
|
setStateAndPersist(s => {
|
||||||
|
@ -346,7 +357,10 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [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}))
|
||||||
|
|
|
@ -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}`))
|
||||||
|
|
|
@ -98,11 +98,14 @@ export const LoginForm = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO remove double login
|
// TODO remove double login
|
||||||
await login({
|
await login(
|
||||||
|
{
|
||||||
service: serviceUrl,
|
service: serviceUrl,
|
||||||
identifier: fullIdent,
|
identifier: fullIdent,
|
||||||
password,
|
password,
|
||||||
})
|
},
|
||||||
|
'LoginForm',
|
||||||
|
)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
const errMsg = e.toString()
|
const errMsg = e.toString()
|
||||||
setIsProcessing(false)
|
setIsProcessing(false)
|
||||||
|
|
|
@ -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}`)}
|
||||||
|
|
|
@ -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',
|
service: 'http://localhost:3000',
|
||||||
identifier: 'alice.test',
|
identifier: 'alice.test',
|
||||||
password: 'hunter2',
|
password: 'hunter2',
|
||||||
})
|
},
|
||||||
|
'LoginForm',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
const onPressSignInBob = async () => {
|
const onPressSignInBob = async () => {
|
||||||
await login({
|
await login(
|
||||||
|
{
|
||||||
service: 'http://localhost:3000',
|
service: 'http://localhost:3000',
|
||||||
identifier: 'bob.test',
|
identifier: 'bob.test',
|
||||||
password: 'hunter2',
|
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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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}`)}
|
||||||
|
|
Loading…
Reference in New Issue