From f2eeefcc17ba3445fdac20f552b1d7b145bf4493 Mon Sep 17 00:00:00 2001 From: Kristof Louviere Date: Thu, 16 Jul 2026 09:00:03 -0600 Subject: [PATCH 1/2] fix: tolerate WhatsApp Web renaming the message key _serialized to $1 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 #201845, #201844, #201846 Co-Authored-By: Claude Opus 4.8 --- src/util/Injected/Utils.js | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/util/Injected/Utils.js b/src/util/Injected/Utils.js index dfa30a579de..3c1d69884d5 100644 --- a/src/util/Injected/Utils.js +++ b/src/util/Injected/Utils.js @@ -582,7 +582,7 @@ exports.LoadUtils = () => { return window .require('WAWebCollections') - .Msg.get(newMsgKey._serialized); + .Msg.get(window.WWebJS.getMsgKeyId(newMsgKey)); }; window.WWebJS.editMessage = async (msg, content, options = {}) => { @@ -625,7 +625,9 @@ exports.LoadUtils = () => { await window .require('WAWebSendMessageEditAction') .sendMessageEdit(msg, content, internalOptions); - return window.require('WAWebCollections').Msg.get(msg.id._serialized); + return window + .require('WAWebCollections') + .Msg.get(window.WWebJS.getMsgKeyId(msg.id)); }; window.WWebJS.toStickerData = async (mediaInfo) => { @@ -917,6 +919,20 @@ exports.LoadUtils = () => { }; }; + /** + * The serialized id of a message key, tolerating WhatsApp Web having renamed the key's + * `_serialized` property to `$1`. Reading the old name now returns `undefined`, which reaches + * IndexedDB as a `.get(undefined)` and throws + * `DataError: Failed to execute 'get' on 'IDBObjectStore': No key or key range specified.` + * `_serialized` is preferred so nothing changes wherever it is still present (chat ids, for + * example, are unaffected by the rename). Returns undefined when neither exists, so callers can + * skip the lookup instead of querying a store with a nullish key. + * @param {Object} key a message key, e.g. `chat.lastReceivedKey` or a raw `msg.id` + * @returns {string|undefined} + */ + window.WWebJS.getMsgKeyId = (key) => + key?._serialized ?? key?.$1 ?? undefined; + window.WWebJS.getChats = async () => { const chats = window.require('WAWebCollections').Chat.getModelsArray(); const chatPromises = chats.map((chat) => @@ -981,16 +997,17 @@ exports.LoadUtils = () => { model.lastMessage = null; if (model.msgs && model.msgs.length) { - const lastMessage = chat.lastReceivedKey + const lastReceivedKeyId = window.WWebJS.getMsgKeyId( + chat.lastReceivedKey, + ); + const lastMessage = lastReceivedKeyId ? window .require('WAWebCollections') - .Msg.get(chat.lastReceivedKey._serialized) || + .Msg.get(lastReceivedKeyId) || ( await window .require('WAWebCollections') - .Msg.getMessagesById([ - chat.lastReceivedKey._serialized, - ]) + .Msg.getMessagesById([lastReceivedKeyId]) )?.messages?.[0] : null; lastMessage && From c8a550cd8d815a4a2a53243c433976b3a18ec795 Mon Sep 17 00:00:00 2001 From: Kristof Louviere Date: Thu, 16 Jul 2026 10:13:19 -0600 Subject: [PATCH 2/2] fix: restore _serialized on the serialized message model after the $1 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 #201846, #201844 Co-Authored-By: Claude Opus 4.8 --- src/util/Injected/Utils.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/util/Injected/Utils.js b/src/util/Injected/Utils.js index 3c1d69884d5..399380926e5 100644 --- a/src/util/Injected/Utils.js +++ b/src/util/Injected/Utils.js @@ -836,6 +836,20 @@ exports.LoadUtils = () => { }); } + // The rename reaches the serialized model too: `msg.id` comes back carrying `$1` and no + // `_serialized`, so every consumer of `message.id._serialized` — including this library's own + // Message structure and anything keyed on a message id — silently receives undefined. Restore + // it here, where `msg.id.remote` is already normalised, so the whole downstream surface keeps + // working rather than each caller having to know about `$1`. + if (typeof msg.id === 'object' && msg.id._serialized == null) { + const serializedId = window.WWebJS.getMsgKeyId(msg.id); + if (serializedId) { + msg.id = Object.assign({}, msg.id, { + _serialized: serializedId, + }); + } + } + delete msg.pendingAckUpdate; return msg;