fix(login): cache switcher user ids so Login runs at most once per account - #1630
Merged
Merged
Conversation
The account switcher named each row by calling the Login RPC signed as that row's account, then GetProfile with the user id it returned (AccountProfileFetcher, from #1626). The app must not sign in as an account the user has not switched to, and iOS dropped the same call. Android stores no user id for accounts other than the signed-in one, so there is no GetProfile call left that avoids Login. Rows now take their username and display name only from AccountProfileCache, which is written while each account is signed in. An account that has never signed in on this device keeps its mnemonic name. The fallback order is unchanged: @username, then display name, then mnemonic name. The balance fetch per row is unchanged. It signs a read as the row's owner key but does not call Login or start a session.
Rows for other accounts could only show the name cached while that account was signed in, because the user id GetProfile needs was never stored. AccountProfileCache now keeps each account's user id alongside its names, taken from UserManager's state while the account is signed in. NoId, the value clear() leaves at sign-out, is skipped. A row with a cached user id fetches its profile with GetProfile by that id, signed with the row's owner key the way the balance fetch signs. A row without one makes no profile call. AccountProfileFetcher now takes the user id and depends only on ProfileRepository and the cache, so it has no path to the Login RPC. Accounts that last signed in before this change have no cached user id until their next sign-in on this device, and keep their cached or mnemonic name until then.
#1625 changed ProfileController.setDisplayName to return the server-assigned username as Result<String?>. NameEntryViewModelTest still stubbed Result<Unit>, so the module's unit tests no longer compiled.
A switcher row with a cached user id still calls GetProfile by that id. A row without one now calls Login signed with its own owner key, caches the user id it returns, then calls GetProfile. The cached id means each account goes through Login at most once on this device. This matches code-payments/code-ios-app#918, which calls login(owner:) only when a row has no stored user id and its database has none either. Android has no database step, so Login follows the cache directly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The account switcher called the Login RPC as every listed account, on every open, to name its row.
AccountProfileFetcher.fetch(added in #1626) calledaccountRepository.login(owner)signed with that row's owner key, thenGetProfileby the returned user id. This PR caches each account's user id so Login runs only for an account with no cached id, and at most once per account on this device. It matches code-payments/code-ios-app#918, which callslogin(owner:)only as a last fallback.Where row names come from now
AccountProfileCachekeeps each account's username and display name, as before, and now its user id too. Both are written fromUserManager's state while that account is signed in.NoId, whichclear()leaves at sign-out, is not stored.GetProfileby that id, signed with the row's owner key the same way the balance fetch signs.GetProfile. A failed Login leaves the row on its cached name, or the mnemonic name.The title order is unchanged:
@username, then display name, then the mnemonic name.Difference from iOS
iOS reads each account's own database read-only before falling back to Login, and that read can also supply a missing user id. Android's per-account Room database has no row marking the account's own profile:
user_profilesis filled from chat members and blocked users and keyed by user id. So on Android, Login follows the cache directly.Also fixed
NameEntryViewModelTeststopped compiling after #1625 changedProfileController.setDisplayNameto returnResult<String?>. The two stubs now returnResult.success<String?>(null). This was failing CI oncode/cashas well as here.Tests
AccountProfileFetcherTest: a cached id goes straight toGetProfilewithout Login; a missing id goes through Login once and caches the returned id; a failed Login makes no profile call and writes nothing;NotFoundmeans "no names"; a failed fetch leaves the cache alone.AccountSelectionViewModelStateTest: a row with a cached id is fetched by that exact id; a row without one is fetched with no id; a failed fetch keeps the cached name.AuthManager.login, which switches the session, is never called.AccountProfileCacheTest: which states record a user id, and decoding stored ids, including malformed ones.