Bluesky app fork with some witchin' additions 💫

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

+98 -48
+5 -1
src/Navigation.tsx
··· 565 565 } 566 566 567 567 function getCurrentRouteName() { 568 - return navigationRef.getCurrentRoute()?.name 568 + if (navigationRef.isReady()) { 569 + return navigationRef.getCurrentRoute()?.name 570 + } else { 571 + return undefined 572 + } 569 573 } 570 574 571 575 /**
+6 -2
src/lib/hooks/useAccountSwitcher.ts
··· 6 6 import * as Toast from '#/view/com/util/Toast' 7 7 import {useCloseAllActiveElements} from '#/state/util' 8 8 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 9 + import {LogEvents} from '../statsig/statsig' 9 10 10 11 export function useAccountSwitcher() { 11 12 const {track} = useAnalytics() ··· 14 15 const {requestSwitchToAccount} = useLoggedOutViewControls() 15 16 16 17 const onPressSwitchAccount = useCallback( 17 - async (account: SessionAccount) => { 18 + async ( 19 + account: SessionAccount, 20 + logContext: LogEvents['account:loggedIn']['logContext'], 21 + ) => { 18 22 track('Settings:SwitchAccountButtonClicked') 19 23 20 24 try { ··· 28 32 // So we change the URL ourselves. The navigator will pick it up on remount. 29 33 history.pushState(null, '', '/') 30 34 } 31 - await selectAccount(account) 35 + await selectAccount(account, logContext) 32 36 setTimeout(() => { 33 37 Toast.show(`Signed in as @${account.handle}`) 34 38 }, 100)
+7
src/lib/statsig/events.ts
··· 2 2 init: { 3 3 initMs: number 4 4 } 5 + 'account:loggedIn': { 6 + logContext: 'LoginForm' | 'SwitchAccount' | 'ChooseAccountForm' | 'Settings' 7 + withPassword: boolean 8 + } 9 + 'account:loggedOut': { 10 + logContext: 'SwitchAccount' | 'Settings' | 'Deactivated' 11 + } 5 12 'notifications:openApp': {} 6 13 'state:background': {} 7 14 'state:foreground': {}
+2 -2
src/screens/Deactivated.tsx
··· 147 147 variant="ghost" 148 148 size="large" 149 149 label={_(msg`Log out`)} 150 - onPress={logout}> 150 + onPress={() => logout('Deactivated')}> 151 151 <ButtonText style={[{color: t.palette.primary_500}]}> 152 152 <Trans>Log out</Trans> 153 153 </ButtonText> ··· 176 176 variant="ghost" 177 177 size="large" 178 178 label={_(msg`Log out`)} 179 - onPress={logout}> 179 + onPress={() => logout('Deactivated')}> 180 180 <ButtonText style={[{color: t.palette.primary_500}]}> 181 181 <Trans>Log out</Trans> 182 182 </ButtonText>
+38 -23
src/state/session/index.tsx
··· 20 20 import {track} from '#/lib/analytics/analytics' 21 21 import {hasProp} from '#/lib/type-guards' 22 22 import {readLabelers} from './agent-config' 23 + import {logEvent, LogEvents} from '#/lib/statsig/statsig' 23 24 24 25 let __globalAgent: BskyAgent = PUBLIC_BSKY_AGENT 25 26 ··· 54 55 verificationPhone?: string 55 56 verificationCode?: string 56 57 }) => Promise<void> 57 - login: (props: { 58 - service: string 59 - identifier: string 60 - password: string 61 - }) => Promise<void> 58 + login: ( 59 + props: { 60 + service: string 61 + identifier: string 62 + password: string 63 + }, 64 + logContext: LogEvents['account:loggedIn']['logContext'], 65 + ) => Promise<void> 62 66 /** 63 67 * A full logout. Clears the `currentAccount` from session, AND removes 64 68 * access tokens from all accounts, so that returning as any user will 65 69 * require a full login. 66 70 */ 67 - logout: () => Promise<void> 71 + logout: ( 72 + logContext: LogEvents['account:loggedOut']['logContext'], 73 + ) => Promise<void> 68 74 /** 69 75 * A partial logout. Clears the `currentAccount` from session, but DOES NOT 70 76 * clear access tokens from accounts, allowing the user to return to their ··· 76 82 initSession: (account: SessionAccount) => Promise<void> 77 83 resumeSession: (account?: SessionAccount) => Promise<void> 78 84 removeAccount: (account: SessionAccount) => void 79 - selectAccount: (account: SessionAccount) => Promise<void> 85 + selectAccount: ( 86 + account: SessionAccount, 87 + logContext: LogEvents['account:loggedIn']['logContext'], 88 + ) => Promise<void> 80 89 updateCurrentAccount: ( 81 90 account: Partial< 82 91 Pick<SessionAccount, 'handle' | 'email' | 'emailConfirmed'> ··· 286 295 ) 287 296 288 297 const login = React.useCallback<ApiContext['login']>( 289 - async ({service, identifier, password}) => { 298 + async ({service, identifier, password}, logContext) => { 290 299 logger.debug(`session: login`, {}, logger.DebugContext.session) 291 300 292 301 const agent = new BskyAgent({service}) ··· 329 338 logger.debug(`session: logged in`, {}, logger.DebugContext.session) 330 339 331 340 track('Sign In', {resumedSession: false}) 341 + logEvent('account:loggedIn', {logContext, withPassword: true}) 332 342 }, 333 343 [upsertAccount, queryClient, clearCurrentAccount], 334 344 ) 335 345 336 - const logout = React.useCallback<ApiContext['logout']>(async () => { 337 - logger.debug(`session: logout`) 338 - clearCurrentAccount() 339 - setStateAndPersist(s => { 340 - return { 341 - ...s, 342 - accounts: s.accounts.map(a => ({ 343 - ...a, 344 - refreshJwt: undefined, 345 - accessJwt: undefined, 346 - })), 347 - } 348 - }) 349 - }, [clearCurrentAccount, setStateAndPersist]) 346 + const logout = React.useCallback<ApiContext['logout']>( 347 + async logContext => { 348 + logger.debug(`session: logout`) 349 + clearCurrentAccount() 350 + setStateAndPersist(s => { 351 + return { 352 + ...s, 353 + accounts: s.accounts.map(a => ({ 354 + ...a, 355 + refreshJwt: undefined, 356 + accessJwt: undefined, 357 + })), 358 + } 359 + }) 360 + logEvent('account:loggedOut', {logContext}) 361 + }, 362 + [clearCurrentAccount, setStateAndPersist], 363 + ) 350 364 351 365 const initSession = React.useCallback<ApiContext['initSession']>( 352 366 async account => { ··· 540 554 ) 541 555 542 556 const selectAccount = React.useCallback<ApiContext['selectAccount']>( 543 - async account => { 557 + async (account, logContext) => { 544 558 setState(s => ({...s, isSwitchingAccounts: true})) 545 559 try { 546 560 await initSession(account) 547 561 setState(s => ({...s, isSwitchingAccounts: false})) 562 + logEvent('account:loggedIn', {logContext, withPassword: false}) 548 563 } catch (e) { 549 564 // reset this in case of error 550 565 setState(s => ({...s, isSwitchingAccounts: false}))
+5
src/view/com/auth/login/ChooseAccountForm.tsx
··· 16 16 import {useProfileQuery} from '#/state/queries/profile' 17 17 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 18 18 import * as Toast from '#/view/com/util/Toast' 19 + import {logEvent} from '#/lib/statsig/statsig' 19 20 20 21 function AccountItem({ 21 22 account, ··· 102 103 Toast.show(_(msg`Already signed in as @${account.handle}`)) 103 104 } else { 104 105 await initSession(account) 106 + logEvent('account:loggedIn', { 107 + logContext: 'ChooseAccountForm', 108 + withPassword: false, 109 + }) 105 110 track('Sign In', {resumedSession: true}) 106 111 setTimeout(() => { 107 112 Toast.show(_(msg`Signed in as @${account.handle}`))
+8 -5
src/view/com/auth/login/LoginForm.tsx
··· 98 98 } 99 99 100 100 // TODO remove double login 101 - await login({ 102 - service: serviceUrl, 103 - identifier: fullIdent, 104 - password, 105 - }) 101 + await login( 102 + { 103 + service: serviceUrl, 104 + identifier: fullIdent, 105 + password, 106 + }, 107 + 'LoginForm', 108 + ) 106 109 } catch (e: any) { 107 110 const errMsg = e.toString() 108 111 setIsProcessing(false)
+4 -2
src/view/com/modals/SwitchAccount.tsx
··· 39 39 track('Settings:SignOutButtonClicked') 40 40 closeAllActiveElements() 41 41 // needs to be in timeout or the modal re-opens 42 - setTimeout(() => logout(), 0) 42 + setTimeout(() => logout('SwitchAccount'), 0) 43 43 }, [track, logout, closeAllActiveElements]) 44 44 45 45 const contents = ( ··· 95 95 key={account.did} 96 96 style={[isSwitchingAccounts && styles.dimmed]} 97 97 onPress={ 98 - isSwitchingAccounts ? undefined : () => onPressSwitchAccount(account) 98 + isSwitchingAccounts 99 + ? undefined 100 + : () => onPressSwitchAccount(account, 'SwitchAccount') 99 101 } 100 102 accessibilityRole="button" 101 103 accessibilityLabel={_(msg`Switch to ${account.handle}`)}
+17 -11
src/view/com/testing/TestCtrls.e2e.tsx
··· 22 22 const {mutate: setFeedViewPref} = useSetFeedViewPreferencesMutation() 23 23 const {setShowLoggedOut} = useLoggedOutViewControls() 24 24 const onPressSignInAlice = async () => { 25 - await login({ 26 - service: 'http://localhost:3000', 27 - identifier: 'alice.test', 28 - password: 'hunter2', 29 - }) 25 + await login( 26 + { 27 + service: 'http://localhost:3000', 28 + identifier: 'alice.test', 29 + password: 'hunter2', 30 + }, 31 + 'LoginForm', 32 + ) 30 33 } 31 34 const onPressSignInBob = async () => { 32 - await login({ 33 - service: 'http://localhost:3000', 34 - identifier: 'bob.test', 35 - password: 'hunter2', 36 - }) 35 + await login( 36 + { 37 + service: 'http://localhost:3000', 38 + identifier: 'bob.test', 39 + password: 'hunter2', 40 + }, 41 + 'LoginForm', 42 + ) 37 43 } 38 44 return ( 39 45 <View style={{position: 'absolute', top: 100, right: 0, zIndex: 100}}> ··· 51 57 /> 52 58 <Pressable 53 59 testID="e2eSignOut" 54 - onPress={() => logout()} 60 + onPress={() => logout('Settings')} 55 61 accessibilityRole="button" 56 62 style={BTN} 57 63 />
+6 -2
src/view/screens/Settings/index.tsx
··· 100 100 {isCurrentAccount ? ( 101 101 <TouchableOpacity 102 102 testID="signOutBtn" 103 - onPress={logout} 103 + onPress={() => { 104 + logout('Settings') 105 + }} 104 106 accessibilityRole="button" 105 107 accessibilityLabel={_(msg`Sign out`)} 106 108 accessibilityHint={`Signs ${profile?.displayName} out of Bluesky`}> ··· 129 131 testID={`switchToAccountBtn-${account.handle}`} 130 132 key={account.did} 131 133 onPress={ 132 - isSwitchingAccounts ? undefined : () => onPressSwitchAccount(account) 134 + isSwitchingAccounts 135 + ? undefined 136 + : () => onPressSwitchAccount(account, 'Settings') 133 137 } 134 138 accessibilityRole="button" 135 139 accessibilityLabel={_(msg`Switch to ${account.handle}`)}