Repository navigation
fix(auth): forward signed-out initial native callbacks - #18797
JulianPscheid wants to merge 2 commits into
Conversation
The Apple and Android auth-state and ID-token stream handlers dropped the first native callback unconditionally. If the SDK had already signed the user out before the Dart streams subscribed (for example after a revoked refresh token on cold start), Dart kept the cached user. Forward a first callback whose user is null, and keep dropping one that carries a user. In the Dart method-channel handlers, track signed-out state per native channel and skip a null that follows a null, so a fresh signed-out app still gets a single initial event. Fixes firebase#18796
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. |
SelaseKay
left a comment
There was a problem hiding this comment.
Hi @JulianPscheid, thanks for the pull request.
The diagnosis is right, and forwarding a signed-out first callback fixes #18796. A few things before this can merge:
1. The new flags can swallow a real sign-out. _authStateChangesIsSignedOut and _idTokenChangesIsSignedOut only become false when a user event arrives from native, but the sign-in methods also set currentUser directly from the returned credential. If a sign-in finishes natively before the event channel listen call is handled (for example await FirebaseAuth.instance.signInAnonymously() as the first use of auth, which is fast against the emulator), this happens:
- The snapshot from
Firebase.initializeApphas no user, so both flags start astrue. - The sign-in completes, and Dart sets
currentUserto the new user. The flags staytrue. - The listener is registered while native already has a user. The first callback carries that user, so native still drops it.
- The app subscribes to
authStateChanges()and gets the user fromcurrentUser. signOut()makes native sendnull. The flag is stilltrue, so Dart drops it, and the stream never emitsnull.
Before this change, step 5 emitted null. A small fix is to clear both flags whenever currentUser is set to a non-null user, for example in a setter, so they track what subscribers could have seen and not only what native has sent. Please add a unit test for this sequence. In the existing test harness, that means calling a sign-in method before injecting any events, then injecting null.
Missing test for the unchanged path. Please also cover that a first native callback carrying a user is still dropped, so a normal signed-in launch emits the user only once. This is the half of the existing behavior that the native change has to keep.
Clear both sign-out deduplication flags whenever currentUser receives a non-null user so a sign-in result cannot hide a later native sign-out. Keep null assignments from changing the flags. Add a unit regression for sign-in before native events across authStateChanges, idTokenChanges, and userChanges. Add a native integration regression that checks the first signed-in callback stays suppressed and a later signed-out callback still arrives. Match swift-format brace placement in both Apple auth stream handlers.
|
Thanks @SelaseKay, good catch on the sign-in path. I've pushed a follow-up commit:
All 204 platform-interface tests pass. |
Description
On Apple and Android, the auth-state and ID-token stream handlers drop the first native callback even when the SDK has already signed the user out. If that sign-out happens before the Dart streams subscribe (for example after a revoked refresh token on cold start), Dart keeps the cached user:
currentUserand the auth streams stay signed in until the app restarts. See #18796.This change forwards a first callback whose user is null and keeps dropping a first callback that carries a user, as before.
Forwarding alone would give a fresh signed-out app a second null event (the case #3628 and the "fires once on first initialization" test guard). So the Dart method-channel handlers now track signed-out state separately for the two native channels and skip a null that follows a null, including the synthetic initial one. The state is per channel because both handlers update the shared
currentUser, their callbacks can arrive in either order, andsignOut()clearscurrentUserbefore the native events arrive. A check oncurrentUseralone would swallow real sign-out events.Tests:
signOut()returns. The package's 201 tests pass.firebase_auth_instance_e2e_test.dartsigns out a fresh named app through the native API before listening, then reads both native channels directly and expects a null first event. It goes through the Pigeon API on purpose, because the public streams prepend a synthetic value and can't show whether the native first callback was forwarded. It passes on macOS and Android (API 36.1 emulator, Auth emulator).Not covered: an automated revoked-refresh-token cold start, iOS runtime, and the full auth e2e suite. The cold-start timing is hard to reproduce in the emulator, so we verified that scenario by hand on macOS in our app.
Related Issues
Fixes #18796
Checklist
///).melos run analyze) does not report any problems on my PR.Breaking Change