Skip to content

fix(login): cache switcher user ids so Login runs at most once per account - #1630

Merged
bmc08gt merged 4 commits into
code/cashfrom
fix/account-switcher-no-background-login
Sep 30, 2026
Merged

bmc08gt merged 4 commits into
code/cashfrom
fix/account-switcher-no-background-login

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

The account switcher called the Login RPC as every listed account, on every open, to name its row. AccountProfileFetcher.fetch (added in #1626) called accountRepository.login(owner) signed with that row's owner key, then GetProfile by 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 calls login(owner:) only as a last fallback.

Where row names come from now

  1. Cache. AccountProfileCache keeps each account's username and display name, as before, and now its user id too. Both are written from UserManager's state while that account is signed in. NoId, which clear() leaves at sign-out, is not stored.
  2. Live fetch by cached id. A row with a cached user id calls GetProfile by that id, signed with the row's owner key the same way the balance fetch signs.
  3. Login fallback. A row with no cached user id calls Login signed with its owner key, caches the returned id, then calls 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_profiles is filled from chat members and blocked users and keyed by user id. So on Android, Login follows the cache directly.

Also fixed

NameEntryViewModelTest stopped compiling after #1625 changed ProfileController.setDisplayName to return Result<String?>. The two stubs now return Result.success<String?>(null). This was failing CI on code/cash as well as here.

Tests

  • AccountProfileFetcherTest: a cached id goes straight to GetProfile without Login; a missing id goes through Login once and caches the returned id; a failed Login makes no profile call and writes nothing; NotFound means "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.

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.
@bmc08gt bmc08gt self-assigned this Sep 30, 2026
@github-actions github-actions Bot added area: auth Login, session, access keys, identity area: onboarding type: fix Bug fix labels Sep 30, 2026
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.
@bmc08gt bmc08gt changed the title fix(login): stop logging in as other accounts to name switcher rows fix(login): name switcher rows without logging in as other accounts Sep 30, 2026
#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.
@bmc08gt bmc08gt changed the title fix(login): name switcher rows without logging in as other accounts fix(login): cache switcher user ids so Login runs at most once per account Sep 30, 2026
@bmc08gt
bmc08gt merged commit 05afee2 into code/cash Sep 30, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: auth Login, session, access keys, identity area: onboarding type: fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant