Skip to content

Conversation

SapphoSys
Copy link

@SapphoSys SapphoSys commented Sep 18, 2025

This pull request adds an account switcher to the post composer. Closes #6708 and closes #5531.

Technical details

When the user clicks on their user avatar, the account switcher is triggered. If there is only one account, the switcher is disabled.

It temporarily assumes an identity just for the composer and goes away if the modal is dismissed. The global site identity remains unaffected.

It uses the same strings as src/components/dialogs/SwitchAccount.tsx. The expired token logic is more or less the same as src/lib/hooks/useAccountSwitcher.ts, only that I temporarily spawn an agent beforehand to check if the identity is valid.

Feature tested on Web and Android. I don't have an iOS device at hand, so I was unable to test the changes there.

Screenshots

Web

msedge_hFg5pBmZBG

Android

(long screenshot) Screenshot_20250918-224358_Bluesky

@SapphoSys SapphoSys changed the title feat: add account switcher feature for composer feat: add an account switcher for composer Sep 18, 2025
@SapphoSys SapphoSys changed the title feat: add an account switcher for composer feat(composer): add an account switcher Sep 19, 2025
@mozzius
Copy link
Member

mozzius commented Sep 22, 2025

holy shit

Comment on lines 200 to 205
session.resumeSession({
...selectedAccount,
accessJwt: selectedAccount.accessJwt,
refreshJwt: selectedAccount.refreshJwt,
active: selectedAccount.active,
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

session.resumeSession() is an async API call - not sure how I feel about doing this in a useMemo. Maybe useQuery might be more appropriate? We should probably await this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah that's a side effect and should not be in useMemo

Copy link
Author

@SapphoSys SapphoSys Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mozzius switched over to tanstack's useQuery if you wanna take a look :)

// try a simple request to check if the session is valid
await tempAgent.getProfile({actor: account.did})
// if it succeeds, update the selected account
setSelectedAccount(account)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we use a query as mentioned above, maybe we could use setQueryData here to reuse the Agent directly so we don't need to resume session twice

Copy link
Author

@SapphoSys SapphoSys Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mozzius done and done in the commits below

Comment on lines 979 to 985
{isWeb ? (
<Menu.Root>
<Menu.Trigger label={_(msg`Switch account`)}>
{({props}) => (
<Button
{...props}
disabled={otherAccounts.length === 0}
Copy link
Member

@mozzius mozzius Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be much neater to just have a single component that is just platform split at the file level for this button+dialog

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 49130ca

createComposerState,
)

const onSelectAccount = React.useCallback(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sure to check what happens if the async code below is slowed down to trigger a race condition of resolving in the opposite order

Copy link
Author

@SapphoSys SapphoSys Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I quickly switched from account A to B to C, then back to A. Switching accounts has a (close to) 1 second delay (might be quicker with your connection, it varied for me), with subsequent switches being a lot faster
(this is with the useQuery & useQueryClient logic added in order to cache sessions)

I also decided to add a 1 second setTimeout promise delay just to check if the rapid fire switch requests would be out of order. I haven't noticed that being the case

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though I could probably add a condition guard or something just in case... hm
@mozzius let me know if it'd be necessary here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to clarify, i mean that race conditions are always possible — so it’s good to find a way to trigger one reliably (eg by simulating alternating delays) and then to write the code in a way that it’s resilient to the race

@haileyok
Copy link
Member

just wanted to say

based

if (selectedAccount.did === currentAccount!.did) {
return defaultAgent
}
const session = new CredentialSession(new URL(selectedAccount.service))
Copy link
Contributor

@mary-ext mary-ext Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this have a listener for session updates, any session expiry here is going to result in the session getting marked as invalid (due to stale refresh token) when you do another post (with that account), or actually switch to that account later

selectedAccount should probably be a DID, instead of passing the entire Account object in

Copy link
Author

@SapphoSys SapphoSys Sep 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya good point, i just quickly put things together to get the switcher working in the composer but now that i look at it its redundant

Copy link
Author

@SapphoSys SapphoSys Sep 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mary-ext done. you can check out commits 1ccdbcc and 06aec44

// get fresh account data from session store
const account = accounts.find(acc => acc.did === accountDid)
if (!account) {
setError('Account not found')
Copy link
Author

@SapphoSys SapphoSys Sep 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not 100% sure about the setError statement here tbh since the codebase only uses setError with either translated strings using msg, or to pass errors just as is (e.message, data.error, etc)
i originally wanted to use the throw statement, but wasn't sure it fit here. this feels a little hacky... hm

Comment on lines +192 to +194
currentAccount?.did,
currentAccount?.service,
currentAccount?.active,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this necessary? queryClients get reset when you change account anyway

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Quick account picker in posts and responses Allow switching accounts from the post composer
5 participants