Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,24 @@ FROM base AS deps
ARG USE_EDGE=false

COPY package*.json ./
COPY patches ./patches

RUN if [ "$USE_EDGE" = "true" ]; then \
apt-get update && apt-get install -y --no-install-recommends git ca-certificates && \
npm ci --only=production --ignore-scripts && \
npm install --save-exact git+https://github.com/pedroslopez/whatsapp-web.js.git#main && \
apt-get purge -y git ca-certificates && apt-get autoremove -y && rm -rf /var/lib/apt/lists/*; \
else \
npm ci --only=production --ignore-scripts; \
apt-get update && apt-get install -y --no-install-recommends patch && \
npm ci --only=production --ignore-scripts && \
WWEBJS_VER="$(node -p 'require("/usr/src/app/node_modules/whatsapp-web.js/package.json").version')" && \
if [ "$WWEBJS_VER" = "1.34.7" ]; then \
echo "Applying WA 2.3000.1043xxx serialized-id compat patch to whatsapp-web.js@$WWEBJS_VER (see patches/README.md)" && \
patch -p1 -d node_modules/whatsapp-web.js < patches/whatsapp-web.js+1.34.7.patch; \
else \
echo "whatsapp-web.js is $WWEBJS_VER, not 1.34.7 - skipping local patch (see patches/README.md)"; \
fi && \
apt-get purge -y patch && apt-get autoremove -y && rm -rf /var/lib/apt/lists/*; \
fi

# Create the final stage
Expand Down
48 changes: 48 additions & 0 deletions patches/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Local library patches

Patches applied to `node_modules` at Docker build time. Applied by the `deps`
stage in the root `Dockerfile` (default build only; the `USE_EDGE=true` build
installs `whatsapp-web.js` from git `main` and is **not** patched).

## `whatsapp-web.js+1.34.7.patch`

**Why:** WhatsApp Web build `2.3000.1043xxx` renamed the internal WID / MsgKey
property `_serialized` to `$1`. `whatsapp-web.js@1.34.7` (latest published) still
reads `_serialized`, so every chat/message operation that goes through the
injected layer (`getChats`, `getChatById`, `fetchMessages`, `downloadMedia`,
message `delete`, etc.) throws a minified `r` error and returns HTTP 500.

**What it does:** backports the dual-compat shim from upstream PR
[wwebjs/whatsapp-web.js#201840](https://github.com/wwebjs/whatsapp-web.js/pull/201840)
(unmerged as of 2026-07-16). Adds two helpers in `Injected/Utils.js`:
- `widSerialized(wid)` - reads `_serialized`, falls back to `$1`.
- `normalizeSerialized(obj)` - mirrors `$1` onto `_serialized` on objects
returned to Node so existing node-side code keeps reading `id._serialized`.

Applied in `getMessageModel` / `getChatModel`, `Message` from/to/author, and the
`sendMessage` return (`Msg.get(newMsgKey._serialized)` returned `undefined` after
the rename, so the send response came back with a null message id).

**API contract:** unchanged. Responses keep every original field
(`id._serialized`, `remote`, `id`, `fromMe`, `from`, `to`, ...). The only
difference is an additive raw `$1` field alongside `_serialized` (same value).

**Version guard:** the Dockerfile applies this patch only when the installed
`whatsapp-web.js` is exactly `1.34.7`. Any other version is skipped with a log
line, so bumping the dependency will not break the build.

## How to revert

Preferred: `git revert` the commit that introduced this directory (removes the
patch file and the Dockerfile change together), then rebuild.

When the upstream fix ships in a release, instead:
1. Bump `whatsapp-web.js` in `package.json` to the fixed version and update
`package-lock.json` (`npm install`).
2. Delete this `patches/` directory.
3. Revert the `deps`-stage patch block in the root `Dockerfile`.
4. Rebuild: `docker compose build api && docker compose up -d --force-recreate`.

Note: even if step 2/3 are skipped, the version guard makes the patch a no-op
once the version is no longer `1.34.7` - so a plain version bump is safe on its
own, and the cleanup can follow later.
166 changes: 166 additions & 0 deletions patches/whatsapp-web.js+1.34.7.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
--- a/src/util/Injected/Utils.js
+++ b/src/util/Injected/Utils.js
@@ -4,6 +4,51 @@
window.WWebJS = {};

/**
+ * Dual-compat serialized id helper.
+ * Older WhatsApp Web builds expose WID/MsgKey as `_serialized`.
+ * Newer builds (e.g. 2.3000.1043xxx) expose the same value as `$1`.
+ * Prefer `_serialized` first so clients that have not updated keep working.
+ * @param {*} wid
+ * @returns {string|undefined}
+ */
+ window.WWebJS.widSerialized = (wid) => {
+ if (!wid || typeof wid === 'string') return wid;
+ return (
+ wid._serialized ??
+ wid.$1 ??
+ (typeof wid.toString === 'function' ? wid.toString() : undefined)
+ );
+ };
+
+ /**
+ * Mirror `$1` onto `_serialized` in plain objects returned to Node so
+ * existing node-side code keeps reading `id._serialized`.
+ * Never overwrites an existing `_serialized` value.
+ * @param {*} obj
+ * @param {number} [depth=0]
+ * @returns {*}
+ */
+ window.WWebJS.normalizeSerialized = (obj, depth = 0) => {
+ if (!obj || typeof obj !== 'object' || depth > 8) return obj;
+ if (Array.isArray(obj)) {
+ for (const item of obj) {
+ window.WWebJS.normalizeSerialized(item, depth + 1);
+ }
+ return obj;
+ }
+ if (obj.$1 !== undefined && obj._serialized === undefined) {
+ obj._serialized = obj.$1;
+ }
+ for (const key of Object.keys(obj)) {
+ const value = obj[key];
+ if (value && typeof value === 'object') {
+ window.WWebJS.normalizeSerialized(value, depth + 1);
+ }
+ }
+ return obj;
+ };
+
+ /**
* Helper function that compares between two WWeb versions. Its purpose is to help the developer to choose the correct code implementation depending on the comparison value and the WWeb version.
* @param {string} lOperand The left operand for the WWeb version string to compare with
* @param {string} operator The comparison operator
@@ -582,7 +627,7 @@

return window
.require('WAWebCollections')
- .Msg.get(newMsgKey._serialized);
+ .Msg.get(window.WWebJS.widSerialized(newMsgKey));
};

window.WWebJS.editMessage = async (msg, content, options = {}) => {
@@ -830,13 +875,13 @@

if (typeof msg.id.remote === 'object') {
msg.id = Object.assign({}, msg.id, {
- remote: msg.id.remote._serialized,
+ remote: window.WWebJS.widSerialized(msg.id.remote),
});
}

delete msg.pendingAckUpdate;

- return msg;
+ return window.WWebJS.normalizeSerialized(msg);
};

window.WWebJS.getChat = async (chatId, { getAsModel = true } = {}) => {
@@ -953,7 +998,7 @@
model.isGroup = true;
const chatWid = window
.require('WAWebWidFactory')
- .createWid(chat.id._serialized);
+ .createWid(window.WWebJS.widSerialized(chat.id));
const groupMetadata =
window.require('WAWebCollections').GroupMetadata ||
window.require('WAWebCollections').WAWebGroupMetadataCollection;
@@ -981,28 +1026,32 @@

model.lastMessage = null;
if (model.msgs && model.msgs.length) {
- const lastMessage = chat.lastReceivedKey
- ? window
- .require('WAWebCollections')
- .Msg.get(chat.lastReceivedKey._serialized) ||
- (
- await window
- .require('WAWebCollections')
- .Msg.getMessagesById([
- chat.lastReceivedKey._serialized,
- ])
- )?.messages?.[0]
- : null;
- lastMessage &&
- (model.lastMessage =
- window.WWebJS.getMessageModel(lastMessage));
+ try {
+ const lastKeyId = window.WWebJS.widSerialized(
+ chat.lastReceivedKey,
+ );
+ const lastMessage = lastKeyId
+ ? window.require('WAWebCollections').Msg.get(lastKeyId) ||
+ (
+ await window
+ .require('WAWebCollections')
+ .Msg.getMessagesById([lastKeyId])
+ )?.messages?.[0]
+ : null;
+ lastMessage &&
+ (model.lastMessage =
+ window.WWebJS.getMessageModel(lastMessage));
+ } catch (_) {
+ // Key-format changes must not break the entire chat model
+ model.lastMessage = null;
+ }
}

delete model.msgs;
delete model.msgUnsyncedButtonReplyMsgs;
delete model.unsyncedButtonReplies;

- return model;
+ return window.WWebJS.normalizeSerialized(model);
};

window.WWebJS.getContactModel = (contact) => {
--- a/src/structures/Message.js
+++ b/src/structures/Message.js
@@ -74,7 +74,7 @@
*/
this.from =
typeof data.from === 'object' && data.from !== null
- ? data.from._serialized
+ ? data.from._serialized ?? data.from.$1
: data.from;

/**
@@ -86,7 +86,7 @@
*/
this.to =
typeof data.to === 'object' && data.to !== null
- ? data.to._serialized
+ ? data.to._serialized ?? data.to.$1
: data.to;

/**
@@ -95,7 +95,7 @@
*/
this.author =
typeof data.author === 'object' && data.author !== null
- ? data.author._serialized
+ ? data.author._serialized ?? data.author.$1
: data.author;

/**