fix: restore message payload and chat lookups on WA Web builds that renamed _serialized - #150
Open
DenysSamoiliuk wants to merge 2 commits into
Open
Conversation
DenysSamoiliuk
marked this pull request as ready for review
August 19, 2026 08:11
WhatsApp Web 2.3000.x renamed the serialized id field of Wid/MsgKey
objects - `$1` in the builds reported upstream. Every `id._serialized`
read then yields undefined: WWebJS.sendMessage ends with
`Msg.get(undefined)`, `client.sendMessage()` resolves to undefined and
`/client/sendMessage` answers `{ success: true }` without the message,
even though the message was sent. `getChatModel` reads
`chat.lastReceivedKey._serialized` the same way, so getChats,
getChatById and downloadMedia throw a minified `r: r` - an IndexedDB
DataError raised by `Msg.getMessagesById([undefined])`.
Restore `_serialized` inside the page right after the `ready` event by
defining it on the Wid and MsgKey prototypes, and normalize the plain
models handed over to node.
The two classes are renamed independently - a build was observed keeping
`_serialized` on Wid while renaming it on MsgKey - so each is probed on
its own. The alias is minifier output rather than a stable name, so it
is discovered by recognising the serialized value instead of assuming
`$1`. Builds that still expose `_serialized` are left untouched.
Refs wwebjs/whatsapp-web.js#201830
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
DenysSamoiliuk
force-pushed
the
fix/wa-web-serialized-id-rename
branch
from
August 19, 2026 12:05
5da503a to
70b6364
Compare
`ready` is not a one-shot event. whatsapp-web.js emits it from `onAppStateHasSyncedEvent`, which WhatsApp Web calls again on every reconnect, and the library explicitly re-injects `window.WWebJS` when it finds the page came back without it. This patch lives on the page's own prototypes, so a reload wipes it - and `client.once` never put it back. In production it went stale roughly a day into each session, and that is exactly when `sendMessage` started handing back nothing: with the `_serialized` getter gone, the `Msg.get(newMsgKey._serialized)` the library runs on the message it has just sent misses, so it returns undefined for a message that was in fact delivered. The guard flag lives on the page, so re-running reports `already applied` while the page still carries the patch and re-applies once it does not. Log the sessionId with the result - two sessions produced a single patch line with no way to tell which one it was.
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 problem
WhatsApp Web 2.3000.x ships minified and renames
_serializedon its id classes to whatever the minifier picked — currently$1. Every id the API hands back loses_serialized, so message payloads, chat lookups and the library's own internalMsg.get(key._serialized)all break.The rename is per class. On the build I am looking at,
Widstill exposes_serializedandMsgKeydoes not — so probing one and assuming the other is wrong.What this does
patchSerializedIdsbuilds a throwaway instance of each class with a known id, then finds the field by value — the one holding the string the serialized id should be. Never by name, because the name is whatever the minifier picked this build. It then installs a_serializedgetter on that prototype, and reports per class what happened:{"applied": true, "wid": {"patched": false, "reason": "exposes _serialized"}, "msgKey": {"patched": true, "alias": "$1"}, "models": ["getMessageModel", "getChatModel", "getContactModel"]}Because the getter sits on the prototype, it also fixes the reads whatsapp-web.js does inside its own injected code — including
Msg.get(newMsgKey._serialized)on a message it has just sent.readyis not a one-shot eventThe second commit is the one that makes this hold up over time, and it took production to find.
whatsapp-web.js emits
readyfromonAppStateHasSyncedEvent, which WhatsApp Web calls again on every reconnect — and the library explicitly re-injectswindow.WWebJSwhen it finds the page came back without it. This patch lives on the page's own prototypes, so a reload wipes it, andclient.once('ready', ...)never put it back.In production the patch went stale roughly a day into each session. That is exactly when
sendMessagestarted handing back nothing: with the getter gone, the lookup on the just-sent message missed, and the library returned undefined for a message that had in fact been delivered.The guard flag lives on the page too, so re-running reports
already appliedwhile the page still carries the patch, and re-applies once it does not. The patch result now logs its sessionId — two sessions produced a single patch line with no way to tell which one it was.Testing
tests/serializedIdPatch.test.jsagainst a fake page: each class renamed independently,_serializedstill writable, models restored, no double application, and a reloaded page getting patched again.Running in production since 2026-08-19.