fix: drive connection lifecycle from WhatsApp's Stream model#201853
Open
Adi1231234 wants to merge 1 commit into
Open
fix: drive connection lifecycle from WhatsApp's Stream model#201853Adi1231234 wants to merge 1 commit into
Adi1231234 wants to merge 1 commit into
Conversation
Connection state was derived from a one-shot `ready` plus the raw offline-delivery percentage. A background (non-blocking) offline sync emits `offline_progress_update`, which was surfaced as `loading_screen` and flipped an already-connected client back to loading. That percentage caps at 99 for a non-blocking resume and `ready` only fires once, so the client never recovered. Derive the state from WhatsApp Web's own Stream model instead (the same `Stream.mode` / `Stream.displayInfo` its loading screen renders from): subscribe to `change:mode change:displayInfo` after ClientInfo exists, map the resolved screen onto the existing `ready` / `loading_screen` events, and fire once for the current state. Backbone only fires change events on real changes, so there is no init race, no backstop and no manual de-dup. QR and error screens keep their own paths. Because `ready` now comes only from real Stream transitions (deduped by last screen), a re-injection can no longer double-emit it. No new events and no signature changes, so existing consumers keep working unchanged.
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.
Description
After a client is ready, an account can get stuck on a permanent loading state even though WhatsApp is connected and messages keep arriving. It comes from how the connection state is derived: from the one-shot
readyevent plus the raw offline-delivery percentage (getOfflineDeliveryProgress()). A background (non-blocking) offline sync firesoffline_progress_updateevents that are surfaced asloading_screen, which flips a connected client back to loading. That percentage caps at 99 for a non-blocking resume, andreadyonly fires once, so it never comes back.WhatsApp Web itself doesn't track the connection like that. Its own loading screen renders from WhatsApp's
Streammodel, readingStream.modeandStream.displayInfo. This does the same thing: it subscribes toStream'schange:mode/change:displayInfoand maps that state onto the existingready/loading_screenevents. It emitsreadywhenmodeisMAINanddisplayInfo === NORMAL,loading_screenwhile it's syncing or offline, and nothing for the QR and error screens (those keep their own paths). The model only fires a change event on an actual change, so there's nothing to de-duplicate by hand.The listener is registered after
ClientInfois built and fires once for the current state. Because it registers after init and only reacts to realStreamchanges, there's no first-connect race, no separate "first ready" path, and nothing to de-dup manually. As a side effect this also generalizes #201653:readynow comes only from realStreamtransitions (deduped by last screen), so a re-injection can't double-emit it.Changes:
Streamchange:mode change:displayInfoand map it onto the existing events: emitreadywhen connected,loading_screenwhile syncing or offline, and nothing for QR or error screens (they keep their own paths)ClientInfoexists and fire once, so a consumer gets the current state without waiting for the next changereadyemit from the app-state-synced handler (the init flow stays, it just no longer decides "connected")offline_progress_updatetoloading_screenpathNo new events and no signature changes, so existing consumers keep working unchanged.
Related
This is a long-standing, frequently reported symptom: WhatsApp Web is connected and its UI is fully loaded, but the client never surfaces
ready, orreadyonly arrives later when a message happens to come in, typically after a restart or with a restored session. Deriving the state from WhatsApp's ownStreammodel and firing once for the current state is what closes that gap: it no longer depends on catching a single one-time transition. Reports of this exact symptom:attachEventListeners/ ready path).readynever fires after auth on v1.34.6 / WA 2.3000.x; commenters noteloading_screenreaches 100% then goes silent after a restart and only fires once a message arrives.readynever fires (still reproduced on recent versions).readynever fires and the client never responds.ready.Prior work this builds on:
ready-on-re-injection fix (now inherent, sincereadyonly follows realStreamtransitions).DISCONNECTEDnow emits aloading_screenevent instead of the state changing silently. This does not address that report's underlying ghost socket (wsReadyState: null); it only makes the state transition observable.For accuracy, these share the same "
readynever fires" symptom but have a different, already-identified root cause and are not addressed here: #3971 / #3798 (an A/B test leavesWAWebSetPushnameConnActionunloaded, so theStore.jsinjection throws), #3958 (local cache corruption), #5717 (missing IndexedDB session blobs).Testing Summary
Test Details
Ran it against a persisted session over several hours, watching the emitted events and the resulting connection state. Boot connects and emits a single
ready; the background offline sync that used to pin it to loading no longer touches the state (zeroloading_screenreverts afterreadyacross the whole run), while incoming messages keep processing. Verified across repeated full re-launches that it reaches connected via exactly oneready, with no duplicate and no listeners left behind.Environment
Type of Change
Checklist
npm test).index.d.ts) have been updated if necessary.example.js) / documentation have been updated if applicable.