Conversation
A tag change on a contact's screen waits for that contact's profile lookup, and an edit's save can still be running when the user signs out. Either then saved through whichever identity was signed in by the time it ran, which may have saved a contact with the same key, and wrote back the local override that sign-out had cleared, or it showed an error toast after the sign-out. Each change now carries the sign-in it was made in. A tag change checks it before and after the lookup, and the repository checks it before the save, passes its identity to the SDK, which checks it under the save's lock, and checks it again as the override and the contact row are written. A change whose sign-in has ended saves nothing, writes no override and shows no toast, also when the same identity signs straight back in.
iOS now applies contact profile refresh results at most every 300 ms and skips lookups for profiles resolved in the last ten minutes, as Android does, so the pubky-profile journeys no longer call either Android only. The contacts list loading journey's description matches the iOS one again.
|
| fun isCurrent(signIn: PubkySignIn): Boolean = | ||
| signInGeneration.get() == signIn.generation && _publicKey.value == signIn.publicKey |
There was a problem hiding this comment.
Old sign-in becomes current again
If a user adopts Ring identity A, switches to B, then adopts A again, a contact edit started during the first A sign-in can still save. Ring adoption changes the public key without advancing the sign-in generation, so the old token matches both checks when A returns. The delayed edit can then write into A’s later sign-in instead of being dropped.
There was a problem hiding this comment.
Fixed in ddb21e6. You were right: adoptRingIdentity published the new key without starting a new sign-in. A sign-in taken under A was current again after A → B → A, and re-adopting A while A was signed in kept it too.
- Every sign-in starts a new one. Ring adoption, identity creation, signup approval and backup restore now call
startSignIn, which advances the generation before publishing the key. iOS feat: one-time stale channel monitor recovery #854 already renews its session revision on each of these. - A refresh keeps the current sign-in. Restoring the stored session at start-up and
refreshSessionIfPossibleusecontinueSignIn. They keep the current sign-in when the key is unchanged, so a session refresh doesn't silently drop an edit that is still saving. When the key changes, including from signed out, they start a new one. - A second gap is closed too.
clearAuthenticatedStateadvances the generation, then suspends before it clears the key. A sign-in taken in that window could become current again after the same identity was restored.
Tests added to PubkyRepoTest:
- After A → B → A, and after re-adopting A while signed in, the old edit fails with
SignInChanged, saves nothing through the SDK and writes no override. Both failed on ee573ac withexpected:<SignInChanged> but was:<null>. - A sign-in taken while sign-out resets the store ends once the identity is restored. This also failed on ee573ac.
- An edit that is saving while its identity's session is refreshed still saves. This one passes on both. It fails if the refresh path starts a new sign-in.
Full unit suite: 3215 tests, 0 failures. detekt is unchanged.
Regtest APKDownload bitkit-dev-debug universal APK (expires in 30 days). |
Adopting a Ring identity published its key without starting a new sign-in, so a sign-in taken under A was current again after adopting B and then A, and a contact edit still waiting from A's first sign-in saved into its later one; re-adopting A while A was signed in kept the earlier sign-in as well. Adoption, identity creation, signup and backup restore now each start a new sign-in before they publish the key, as on iOS, which also ends a sign-in taken while a sign-out was clearing the store. Refreshing or restoring the session of the identity already signed in keeps its sign-in, so an edit under way for that identity still saves; it starts a new one only when the key changes.
This PR drops a contact tag change or edit that is still waiting to save when the Pubky sign-in it was made in ends, so it can no longer save through the next identity or write back what sign-out cleared.
It follows #1399. QA found this race on iOS #854 (thread), and the QA review of #1399 judged that Android was not affected. Android does have it: the new
ContactSaveSessionChangeTestfails 6 of 6 on b46f007, the head #1399 merged with.Description
PubkySignIn, which holds the signed-in public key and a sign-in generation. It holds no secret.clearAuthenticatedStateadvances the generation before anything else, so sign-out and a wipe end the sign-in.PubkyRepo.updateContactnow requires it and checks it:expectedIdentityand checks it under the same lock as the write;contactsLock.PubkyContactError.SignInChangedand is logged at info level.pubky-profilejourney README and incontacts-list-loading.xml, since iOS feat: one-time stale channel monitor recovery #854 does both too. The 10 s import-preview deadline is still Android only.Out of Scope
EditContactViewModel.kt: edits typed while signed in as one identity can still be saved by a Save tap made after another identity has signed in. That Save binds to the new sign-in. It is hard to reach: sign-out pops back to the contact screen, and adopting a Ring identity pops everything above Home. It would need the sign-in taken when the form loads, or the form closed when the sign-in changes.PubkyRepo.kt:addContactandremoveContactdo not carry a sign-in. WhenaddContactgets no profile from its caller, it looks the contact up before saving, so a sign-out during that lookup leaves the same window. Nobody has reported it, and it is left for a separate change.PubkyRepo.kt: adopting a Ring identity while another is signed in keeps the previous identity's contact overrides, becauseclearProfileIfIdentityChangedleavespubkyStorealone. The contacts load also applies them without checkingownerPublicKey. This predates this PR, and it is hard to reach the same way: the choice screen redirects once you are signed in.Design
N/A — no UI changes.
Preview
N/A
QA Notes
Journeys
N/A — not drivable; see Manual Tests.
Manual Tests
Automated Checks
ContactSaveSessionChangeTest.kt— runs the realPubkyRepounder the real view models, with a fake SDK that saves only for the signed-in identity. Tags queued before a sign-out save nothing when the next identity has the same contact, when it lacks it, and when the same identity signs back in. Tags waiting on a lookup that sign-out stops save nothing and show no toast. A tag save or an Edit Contact save that a session change overtakes leaves the next identity aloneContactDetailViewModelSignInTest.kt— a tag change whose sign-in ends while the contact loads does not look it up or save; one whose sign-in ends during the lookup saves nothing; a save that fails after its sign-in ended shows no toastPubkyRepoTest.kt:ContactDetailViewModelTest.kt— stubs the sign-in for the newupdateContactparameterEditContactViewModelTest.kt— stubs the sign-in for the newupdateContactparametertestDevDebugUnitTest— 3215 tests, 0 failures.ContactSaveSessionChangeTestfails 6 of 6.