v0.12.1
Added
-
The session payload reports whether the gateway holds a live engine (
engineLoaded). This is the
precondition the lifecycle routes actually enforce, andstatuswas never a reliable proxy for it:
disconnectedmeans both "the engine is still registered while an automatic reconnect backs off,
andstarttherefore answers 400" and "the session was stopped and has no engine, sostartis
exactly the right call". The dashboard now derives Stop, Unlink, Force-Kill and Start from the field
instead of guessing from the status, which closes the case where a session dropped mid-reconnect
offered only Start, got a 400, and then left a QR dialog open that could never resolve — and, in the
other direction, hid Force-Kill from precisely the wedged engine that button exists for. Added to
openapi.jsonand to the JavaScript, Python, Go and Java SDKs (the PHP SDK returns untyped arrays);
it is a new optional field on the response, so existing clients are unaffected. A dashboard served
by an older gateway falls back to the previous status-based rule rather than reading a missing field
as "no engine". -
The whatsapp-web.js onboarding modal can be dismissed on a non-English WhatsApp Web. The detector
matches the modal's visible confirm label, which isContinueonly while the page renders in
English. Two changes: Chromium is now launched with a pinned--langso the page locale is
deterministic across the amd64 and arm64 images (an explicit--langinPUPPETEER_ARGSstill
wins, and the pin is applied even when that variable replaces the default flags — otherwise
customising args for an unrelated reason would silently drop it); and
WWEBJS_ONBOARDING_CONTINUE_LABELSaccepts additional labels for a deployment whose modal is
localised anyway. An operator-supplied label is matched without the English heading check, which
would reject the very modal it targets; the defaultContinuekeeps that check, so the
out-of-the-box false-positive surface is unchanged.
Fixed
-
A session awaiting operator action is watched again, without being taken over. The liveness
watchdog skipped every non-readysession, so once a session moved toaction_requiredits page
was never probed again — if it died while waiting for a human, nothing recorded that anywhere. It is
now probed, but the result is observed only: a failure is logged and never accrues toward the
disconnect threshold, never publishes asession.disconnected, and never schedules a reconnect.
Acting on it would hand the session to the reconnect path and silently clear the very status that
asked for attention, which stays the operator's to clear with a stop and a start. The warning is
emitted once per unresponsive stretch rather than once per probe, since a session can legitimately
sit in that state until someone reaches it, and a recovery is logged so an earlier warning is not
left as the last word. -
A delete refused while a credential teardown is in flight no longer leaves a status it cannot
honour. That refusal (409 SESSION_NAME_TEARDOWN_PENDING) happens after the engine has already
been destroyed and evicted, so the surviving row kept readingready— or whatever it was — for a
session with nothing behind it, until something else wrote the status. The row is now reconciled to
disconnectedbefore the refusal propagates. The earlier of the two fences needs no such handling:
nothing has run by then, so its status is still accurate. -
A WhatsApp-initiated logout that arrives during an unrelated teardown no longer hides its
credential removal. whatsapp-web.js emitsdisconnected: LOGOUTand then runs
authStrategy.logout()→fs.rmof the session's profile directory, and that removal happens
regardless of what the adapter's listener does. The listener returned early whenever a teardown had
already latched — whichstop(),destroy()andforce-killall do — so in that window the
in-flight removal was never registered, the name-keyed teardown fence saw nothing pending, and a
followingstart()under the same name could have its freshly written credentials deleted by it.
This is the same hazard the fence was built for, reached through a narrower window. The removal is
now surfaced before the latch check, and skipped only when the adapter's ownlogout()started it —
that path already registers the realClient.logout()promise, which covers the same removal. -
The dashboard no longer fabricates a session status after a start. It wrote a local
status: 'connecting'— a value the gateway does not emit — while keeping every other field from
before the request, and used the authoritative response for nothing. It now applies the response as
returned, the same rule the stop and unlink paths already follow. A live status push also drops the
stale engine answer that arrived with it, and adisconnectedpush refreshes, because that status
alone cannot say which of its two meanings applies. -
Removed two session statuses from the dashboard that the gateway never emits (
connecting,idle),
along with the dead branches that tested for them. -
Internal tidying with no behaviour change: a caller-initiated logout no longer registers its
credential removal twice (the lifecycle already tracks the wholeengine.logout()promise, which is
the only registration that covers both engines), and a duplicated engine-ownership check in the
pre-initialize window is gone — it sat behind a synchronous check that cannot change the answer, so
it could never disagree with the one before it.