Added
Changed
- Send-response semantics clarified (docs only). The send endpoints' Swagger response and
docs/06now state explicitly that201means the gateway accepted the message for sending — not that the recipient received it — and that WhatsApp does not reject an unregistered recipient synchronously, so a message to a number that is not on WhatsApp still returns201with amessageIdbut never delivers.GET /sessions/{id}/contacts/check/{number}is cross-referenced as the way to pre-validate a new recipient, and the async messagestatuslifecycle (sent → delivered → read, orfailed) as the source of real delivery state. No behavior change. Refs #738.
Fixed
-
Inbound media download, message ids, acks, and reply quoting restored
(whatsapp-web.jsid._serialized→id.$1rename). WhatsApp Web build 2.3000.x
(rolled out ~2026-07-14) renamed the internal serialized message-id property
fromid._serializedtoid.$1, which broke whatsapp-web.js 1.34.7's
downloadMedia(), message-id extraction, ack tracking, and quoted-message
resolution for every bot at once. The production Docker image now backports
upstream fix #201832
(Base._normalizeId) into the installed dependency at build time via
scripts/patch-wwebjs-201832.js. The patcher applies the real upstream diff
(with a loud-fail guard against version skew) and auto-disables the moment a
future whatsapp-web.js release ships the fix, so it is a stopgap, not a fork.
Fixes #747. -
Source installs get the whatsapp-web.js backport too, not just the Docker image.
npm installnow applies it frompostinstall, so the local-development setup in
the README is no longer stuck with broken media downloads. It is best-effort
there: a machine without apatchbinary (Windows outside WSL) or a
Baileys-only setup gets a warning rather than a failed install. The image build
still treats the same failure as fatal. -
Reactions stay attributable on the renamed-id builds too.
Reactionassigns
its keys straight through, so it is the one structure upstream's normalization
doesn't reach; the adapter now reads the renamed field directly and falls back to
the empty no-id sentinel instead of passingundefinedon. -
A reaction with no message id no longer updates an arbitrary message.
applyReactionlooked the message up by an id that could beundefined, and
TypeORM drops an undefined condition from the where-clause rather than matching
nothing — so the lookup found an unrelated row and emitted its reactions under
the incoming event. The id is now checked before the query. Latent since
reactions were added; only reachable when an engine can't resolve the id. -
Engine start timeouts now return a diagnostic 504 instead of a bare 500. Two
POST /api/sessions/:id/startfailure modes previously escaped to NestJS's default handler as a
meaningless500 Internal Server Error: (1) the auth-timeout — whatsapp-web.js throws the
primitive string'auth timeout'when its login poll exhaustsauthTimeoutMs(default 30s), e.g.
an unreachable session proxy means the browser launches but no QR is ever delivered; and (2) the
outer init-hang deadline (EngineInitTimeoutError) — a wedgedinitialize()that never settles
withinmax(60s, WWEBJS_AUTH_TIMEOUT_MS+30s), usually a container memory/resource limit or a stalled
Chromium. Both now map to504 Gateway Timeoutwith a diagnostic message (proxy/network vs resource
limits respectively) and theWWEBJS_AUTH_TIMEOUT_MSknob for slow first boots. Engine cleanup
(force-destroy + evict + status) still runs before mapping; generic non-timeout init rejections
(e.g. "chromium launch failed") still propagate untouched. -
S3 storage no longer falls back to local without an
endpoint. The S3 client init required an
endpoint, which only S3-compatible stores (MinIO, R2) need — standard AWS S3 (whose endpoint is
derived from region) silently initialized no client and served all media from local disk (#735).
endpointandforcePathStyleare now applied only when an endpoint is configured, so AWS S3 uses
its default virtual-hosted addressing while MinIO-compatible stores keep path-style. -
.env.exampleno longer ships a defaultS3_ENDPOINT. The template's pre-filled
http://localhost:9000silently re-routed a copy-paste AWS S3 config to MinIO/path-style mode and the
local fallback; it is now commented out so the default is AWS virtual-hosted mode (#735 follow-up). -
WhatsApp Engine selection on the Infrastructure page no longer reverts to the running engine.
The engine radio was re-stamped from the live/engines/currentvalue on every emission, so a late
first resolution (or a window-focus refetch) overwrote an operator's in-progress, unsaved selection
(#735). The selection now seeds once and freezes on the first user interaction, matching the
one-time hydration lock the other infrastructure fields already had. -
Message Tester supports uploading local media files. Media messages could previously only be
sent from a URL; a file picker is now available alongside the URL field (mutually exclusive with
it), reading the file as base64 (#735). The backend already acceptedbase64; this adds the
dashboard UI for it. Uploads are client-capped at 18 MiB (the effective base64-over-JSON body
limit) so an oversized pick surfaces a clear error instead of freezing the tab, and switching the
message category after picking a file now clears it so stale bytes aren't routed to the wrong
endpoint.
Security
- Plugin archive extraction hardened against CVE-2026-39244 (adm-zip declared-size zip-bomb OOM).
Theadm-zipbump to0.6.0in #728 brings upstream's fix for CVE-2026-39244: a crafted archive
declaring a huge entry size could drive an unboundedBuffer.allocand exhaust memory during
extraction. This closes the declared-size allocation vector on the plugin marketplace install path
(src/modules/plugins/plugin-installer.ts), complementing the project's ownreadEntryData()guard
that already caps decompressed bytes via zlibmaxOutputLength. The two adm-zip 0.6.0 behavior
changes (extractEntryTosubdirectory preservation, non-fatalutimes) touch APIs this project does
not use. The now-redundant@types/adm-zipdevDependency is dropped as well — adm-zip 0.6.0 ships its
owntypes.d.ts.