Skip to content

fix: tolerate WhatsApp Web renaming the message key _serialized to $1 - #201848

Open
kristoflouviere wants to merge 2 commits into
wwebjs:mainfrom
kristoflouviere:fix/msgkey-serialized-rename
Open

fix: tolerate WhatsApp Web renaming the message key _serialized to $1#201848
kristoflouviere wants to merge 2 commits into
wwebjs:mainfrom
kristoflouviere:fix/msgkey-serialized-rename

Conversation

@kristoflouviere

Copy link
Copy Markdown

The bug

WhatsApp Web renamed the message key's _serialized property to $1.

getChatModel reads chat.lastReceivedKey._serialized (Utils.js#L984-L997), which now yields undefined and reaches IndexedDB as Msg.getMessagesById([undefined]):

DataError: Failed to execute 'get' on 'IDBObjectStore': No key or key range specified.

getChats() maps getChatModel over every chat under Promise.all, so one affected chat rejects the entire batch — surfacing to callers as an opaque minified error (widely reported as r: r) while the client is CONNECTED and WhatsApp Web is fully loaded.

Chat ids are not affected — they still carry _serialized. That is why this doesn't look like the $1 rename, and why fixes that normalize ids in the Node-side structures/ layer don't reach it: the throw happens in-page, inside getChatModel, before anything is returned.

Evidence

Measured on two live sessions (WA Web 2.3000.1043280533, whatsapp-web.js 1.34.7) by catching the error inside page.evaluate so the in-page stack survives the CDP boundary (otherwise it collapses to name: message, i.e. r: r):

chats with lastReceivedKey of those, having _serialized getChatModel result
session A 1253 916 0 916 threw · 337 ok
session B 515 498 0

Chats with no last message skip the block entirely — which is exactly the observed 337 / 916 split. Sampled keys look like:

keys: [fromMe, remote, id, $1]      _serialized: undefined      $1: "true_1252928…@lid_3A7…"

Re-running the same lookup with _serialized ?? $1 returned the message on every affected chat.

The fix

Adds window.WWebJS.getMsgKeyId(key):

  • prefers _serialized, so behaviour is unchanged wherever it is still present;
  • returns undefined when neither exists, so callers skip the lookup rather than querying a store with a nullish key.

Applied to the three places a raw (un-serialize()d) message key is read: getChatModel, sendMessage, editMessage.

getMessageModel is deliberately untouched — it reads a serialize()d model, which still produces _serialized. msg.id.remote._serialized is also untouched: remote is a chat id, which the rename didn't affect.

Only getChatModel is verified against live sessions; sendMessage/editMessage read a raw key of the same shape and get the same one-token change. The helper is a no-op where _serialized still exists, so it cannot regress either path.

Refs #201845, #201844, #201846


Prettier + the existing style are unchanged (prettier --check passes; diff is 1 file, +24/−7).

WhatsApp Web renamed the message key's `_serialized` property to `$1`. getChatModel
reads `chat.lastReceivedKey._serialized`, which now yields undefined and reaches
IndexedDB as `Msg.getMessagesById([undefined])`, throwing:

  DataError: Failed to execute 'get' on 'IDBObjectStore': No key or key range specified.

getChats() maps getChatModel over every chat under Promise.all, so a single affected
chat rejects the whole batch and surfaces to callers as an opaque minified error
(commonly reported as "r: r") while the client is CONNECTED and WhatsApp Web is fully
loaded. Chat ids are NOT affected -- they still carry `_serialized` -- which is why the
failure does not look like the rename.

Measured on two live sessions (WA Web 2.3000.1043280533, whatsapp-web.js 1.34.7), by
catching the error inside page.evaluate so the in-page stack survives:

  session A: 1253 chats, 916 with a lastReceivedKey, 0 of which had `_serialized`
             -> getChatModel threw for those 916 and succeeded for the other 337
  session B:  515 chats, 498 with a lastReceivedKey, 0 with `_serialized`

Chats with no last message skip the block entirely, which is exactly the observed
337/916 split. Reading `_serialized ?? $1` resolved the key on every affected chat.

Adds window.WWebJS.getMsgKeyId(key). It prefers `_serialized`, so behaviour is unchanged
wherever that is still present, and returns undefined so callers skip the lookup instead
of querying a store with a nullish key. Applied to the raw message keys read by
getChatModel, sendMessage and editMessage (getMessageModel is unaffected: it reads a
serialize()d model, which still produces `_serialized`).

Refs wwebjs#201845, wwebjs#201844, wwebjs#201846

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added api changes API modifications utility Utility code labels Jul 16, 2026
… rename

The rename reaches getMessageModel too: `message.serialize()` returns an `id` carrying
`$1` and no `_serialized`, so every consumer of `message.id._serialized` -- including this
library's own Message structure, and any caller keying on a message id -- silently gets
undefined.

Measured on a live session (WA Web 2.3000.1043280533, 2753 messages in memory): every
serialized model came back with `id: { fromMe, remote, id, $1 }` and `_serialized:
undefined`.

The failure mode is quiet and severe rather than loud: code that dedupes on
`msg.id._serialized` sees the same undefined key for every message, so a consumer keeping
a Set of seen ids processes exactly one message and silently drops the rest -- no error,
no log, just an ingestion path that looks alive and captures nothing.

Restores `_serialized` in getMessageModel next to the existing `msg.id.remote`
normalisation, using the same getMsgKeyId helper, so the whole downstream surface keeps
working instead of each caller having to learn about `$1`. Only fills it in when absent,
so nothing changes once/if WhatsApp Web restores the old name.

Refs wwebjs#201846, wwebjs#201844

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@jonnytest1 jonnytest1 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@dolav

dolav commented Jul 17, 2026

Copy link
Copy Markdown

Tested this and it perfectly fixes the r: r IndexedDB crashes on my headless Oracle Cloud setup. +1 for this surgical approach!

@vaibhavpandeyvpz

vaibhavpandeyvpz commented Jul 17, 2026

Copy link
Copy Markdown

It worked perfectly for me. Thanks! But this ultimately requires patch in #201847 as well

@vaibhavpandeyvpz

Copy link
Copy Markdown

Few misses still:

#1 — Poll votes

This is a real uncovered path, assuming pollUpdateParentKey underwent the same _serialized$1 rename.

When a live poll-vote update arrives, Client.js does:

const parentMsgKey = vote.pollUpdateParentKey;

let parentMessage = Msg.get(parentMsgKey._serialized);
if (!parentMessage) {
    const fetched = await Msg.getMessagesById([
        parentMsgKey._serialized,
    ]);
}

If that key now looks like:

{ $1: '...', remote: ..., id: ... }

both lookups receive undefined. Unlike issue #1, the first lookup occurs directly in the event-processing path, so it may throw and prevent the poll-vote event from being emitted.

The resulting PollVote also exposes the key unchanged:

this.parentMsgKey = data.parentMsgKey;

So consumers reading vote.parentMsgKey._serialized would also get undefined.

Scope

This mainly affects:

  • live vote_update/poll-vote events;
  • resolving the poll creation message associated with a vote;
  • consumers accessing the vote’s parent message ID.

It does not necessarily break message.getPollVotes(). That method begins with the poll message’s primary msg.id, which your getMessageModel normalization already repairs. The returned vote objects may still expose an unnormalized parentMsgKey, though.

Suggested minimal fix

Use getMsgKeyId in the injected poll-vote handler and normalize the key before it crosses into PollVote:

const parentMsgKeyId = window.WWebJS.getMsgKeyId(parentMsgKey);

let parentMessage = parentMsgKeyId
    ? Msg.get(parentMsgKeyId)
    : null;

if (!parentMessage && parentMsgKeyId) {
    const fetched = await Msg.getMessagesById([parentMsgKeyId]);
    parentMessage = fetched?.messages?.[0] || null;
}

Then either serialize parentMsgKey there or normalize it in PollVote, as done for Reaction.


#2 — Nested message IDs

There are two nested fields:

message.latestEditMsgKey
message.protocolMessageKey

Both are declared as MessageId, meaning callers reasonably expect:

key._serialized

Your normalization in getMessageModel repairs only:

msg.id

It does not recursively repair those nested keys.

latestEditMsgKey

This appears to be mostly an exposed-data/API consistency issue. Within whatsapp-web.js@1.34.7, it is assigned but not otherwise consumed:

this.latestEditMsgKey = data.latestEditMsgKey;

Therefore, the library itself probably continues working. Only consumers that access:

message.latestEditMsgKey._serialized

would observe the break. If your project does not use that property, you can likely ignore it.

protocolMessageKey

This one has a narrow internal consequence during “delete for everyone” events:

if (message.protocolMessageKey)
    revoked_msg.id = { ...message.protocolMessageKey };

If protocolMessageKey contains only $1, the emitted revoked_msg.id also lacks _serialized. Consequently:

revokedMessage.id._serialized

would be undefined for listeners of message_revoke_everyone.

It likely does not prevent the revocation event itself from firing. It mainly leaves the reconstructed revoked message with an incomplete public ID.

Practical priority

Path Likely impact Recommendation
Poll-vote parent key Event processing may fail Worth fixing if polls matter
protocolMessageKey Revoked message ID missing Fix if handling delete-for-everyone events
latestEditMsgKey Public nested ID missing Low priority unless consumed

@vaibhavpandeyvpz

Copy link
Copy Markdown

This is the patch that's working for me https://github.com/vaibhavpandeyvpz/wappmcp/tree/main/patches until changes in this PR are released

@jonnytest1

Copy link
Copy Markdown

Btw , has there been a discussion about bundling the current whatsappweb into the package , or agressively caching them post deploy so upstream changes arent as likely to break things ? (I imagine whatsapp has to keep up the older apis for a while anyways ) 🤔

@Adi1231234

Copy link
Copy Markdown
Contributor

Confirmed @vaibhavpandeyvpz's poll-vote point on a live 2.3000.x session: vote.pollUpdateParentKey is a MsgKey with _serialized now undefined (the value moved to $1), so Msg.getMessagesById([undefined]) throws the same r. Running it through getMsgKeyId resolves it.

Happy to help however's easiest for you - I can open a full PR with all the fixes bundled together, or if you'd rather keep everything in this one, I'm glad to send the changes your way. Whatever works best. 🙂

@XxQuickSilverZz

Copy link
Copy Markdown

Confirmed @vaibhavpandeyvpz's poll-vote point on a live 2.3000.x session: vote.pollUpdateParentKey is a MsgKey with _serialized now undefined (the value moved to $1), so Msg.getMessagesById([undefined]) throws the same r. Running it through getMsgKeyId resolves it.

Happy to help however's easiest for you - I can open a full PR with all the fixes bundled together, or if you'd rather keep everything in this one, I'm glad to send the changes your way. Whatever works best. 🙂

please tell me all changes...

@ShoyebOP ShoyebOP mentioned this pull request Jul 25, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api changes API modifications utility Utility code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants