Skip to content

fix: restore message payload and chat lookups on WA Web builds that renamed _serialized - #150

Open
DenysSamoiliuk wants to merge 2 commits into
avoylenko:mainfrom
DenysSamoiliuk:fix/wa-web-serialized-id-rename
Open

fix: restore message payload and chat lookups on WA Web builds that renamed _serialized#150
DenysSamoiliuk wants to merge 2 commits into
avoylenko:mainfrom
DenysSamoiliuk:fix/wa-web-serialized-id-rename

Conversation

@DenysSamoiliuk

@DenysSamoiliuk DenysSamoiliuk commented Aug 19, 2026

Copy link
Copy Markdown

The problem

WhatsApp Web 2.3000.x ships minified and renames _serialized on 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 internal Msg.get(key._serialized) all break.

The rename is per class. On the build I am looking at, Wid still exposes _serialized and MsgKey does not — so probing one and assuming the other is wrong.

What this does

patchSerializedIds builds 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 _serialized getter 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.

ready is not a one-shot event

The second commit is the one that makes this hold up over time, and it took production to find.

whatsapp-web.js emits ready 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('ready', ...) never put it back.

In production the patch went stale roughly a day into each session. That is exactly when sendMessage started 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 applied while 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.js against a fake page: each class renamed independently, _serialized still writable, models restored, no double application, and a reloaded page getting patched again.

npm test  → 47 passed
npx eslint src tests

Running in production since 2026-08-19.

@DenysSamoiliuk
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
DenysSamoiliuk force-pushed the fix/wa-web-serialized-id-rename branch from 5da503a to 70b6364 Compare August 19, 2026 12:05
@DenysSamoiliuk DenysSamoiliuk changed the title fix: restore message payload on WA Web builds that renamed _serialized to $1 fix: restore message payload and chat lookups on WA Web builds that renamed _serialized Aug 19, 2026
`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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant