Skip to content

fix: coerce text parameters, and correct copy that misstated server behaviour - #48

Merged
rmyndharis merged 1 commit into
mainfrom
fix/pre-release-hardening
Aug 25, 2026
Merged

fix: coerce text parameters, and correct copy that misstated server behaviour#48
rmyndharis merged 1 commit into
mainfrom
fix/pre-release-hardening

Conversation

@rmyndharis

Copy link
Copy Markdown
Owner

A pre-release pass over everything added since v0.9.3, driven by source-level tracing rather than diff reading. Three classes of problem came out of it.

Any expression-driven parameter could crash the node

Thirty-five places read a text parameter as (x as string).trim(). An expression resolving to a number, a boolean or null made that a TypeError, which the executor then wrapped as a server API error naming no field — so the user went looking at their gateway for a problem that was in their expression.

All thirty-five now go through one shared coercion. It is identical for a string; for anything else it stringifies, so a numeric chat ID or session ID simply works, and the existing emptiness and length checks still give a pointed message where the value really is unusable.

contact/checkExists  phoneNumber: 628123456789   -> /contacts/check/628123456789
session/getStatus    sessionId:   12345          -> /api/sessions/12345
message/sendText     chatId:      628123456789   -> {"chatId":"628123456789",...}

Two requests the server would have refused

  • Webhook Update forwarded headers: null. The column is NOT NULL and rejects it, surfacing as an opaque 500. The clearing gesture the sibling Filters field documents now sends the empty object the column actually takes.
  • Template Update could send an empty patch and report success. It assigned the helper's "not supplied" result straight into the body, so the key stayed present, the all-empty guard never fired, and an update whose only field resolved to null went out as {} — a 200 that changed nothing. That is the exact outcome the helper exists to prevent.

Copy that was factually wrong

Each was checked against the server source or the capability matrix, not assumed:

Said Actually
Contact Save is "stored on the gateway only" It writes the WhatsApp account's addressbook and syncs to linked devices. Only the phone's native contacts are untouched.
Unsubscribe "can be reversed by subscribing again" Subscribe is Baileys only, so on whatsapp-web.js it is one-way. This sat next to a destructive operation.
Pin Chat: "check the returned success flag" True on whatsapp-web.js. On Baileys the flag is hard-coded true, so a fourth pin reports success and does not stick.
Filters: "use a second Trigger" The delivery path is derived from the session, so two Triggers on one session collide on the same URL.
Automation Rule: a blank Conditions "matches every message" An update sends only what you fill in, so a blank leaves the stored conditions alone.
Media conversion notice Described only the voice output while also shown on Convert to Video.
Status Recipients: "every contact" That account's usual status audience.
README: one mentions floor Reply, Edit and Send Template need ≥ 0.23.0; the sends have accepted it since 0.7.14. The README also still claimed the node "does not surface" four operations it now ships.

Also

Automation-rule conditions use the shared JSON reader, so the two filter-shaped fields no longer disagree on whitespace. stableStringify answers for a BigInt instead of throwing, which it had documented as a guarantee, and is now cycle-safe and consistent with a JSON round trip.

Verification

431 tests (up from 426), lint, build, dist-freshness and the Creator Portal scanner all pass. Beyond the suite, these ran clean:

  • every request body validated against its server DTO: no undeclared fields, no missing required fields, no wrong types or enums
  • every loadOptionsMethod, displayOptions target and options default resolves; no duplicate properties
  • the README operations table matches the code exactly, 157/157
  • no operation reads a parameter the UI hides
  • no secrets, internal hostnames or non-synthetic data anywhere in the range

…ehaviour

A pre-release pass over everything since v0.9.3.

**Any parameter driven by an expression could crash the node.** Thirty-five
places read a text parameter as `(x as string).trim()`. An expression resolving
to a number, a boolean or null made that a TypeError, which the executor then
re-labelled as a server API error naming no field, so the user was sent looking
at their gateway for a problem in their expression. Every one now goes through a
shared coercion: identical for a string, and for anything else it stringifies so
a numeric chat ID or session ID simply works, leaving the existing emptiness and
length checks to give a pointed message where the value really is unusable.

**Two requests the server would refuse.** Webhook Update forwarded `headers:
null`, which the column is NOT NULL and rejects; the clearing gesture the sibling
field documents now sends the empty object the column takes. Template Update
assigned its helper's "not supplied" result straight into the body, so the key
stayed present, the all-empty guard never fired, and an update whose only field
resolved to null went out as `{}` and reported success while changing nothing.

**Copy that was factually wrong.** Each was checked against the server:

* Contact Save said the entry is "stored on the gateway only". It is a write to
  the WhatsApp account's own addressbook that syncs to the linked devices. Only
  the phone's native contacts are untouched.
* The Channel notice said Unsubscribe "can be reversed by subscribing again".
  Subscribe is Baileys only, so on whatsapp-web.js unsubscribing is one way.
* Pin Chat told users to check the returned success flag. That works on
  whatsapp-web.js; on Baileys the flag is always true and the three-chat cap is
  invisible, so a fourth pin reports success and does not stick.
* The Filters help suggested a second Trigger for the events a filter would
  swallow. The delivery path is derived from the session, so two Triggers on one
  session collide on the same URL.
* Automation Rule Update said a blank Conditions field matches every message. An
  update only sends what you fill in, so a blank leaves the stored conditions
  alone.
* The media conversion notice described only the voice output while sitting on
  Convert to Video as well.
* Status Recipients said whatsapp-web.js posts to "every contact"; it posts to
  that account's usual status audience.
* The README gave one mentions floor for a list that now spans two: Reply, Edit
  and Send Template need a much newer server than the sends do. It also still
  said the node does not surface four operations it now ships.

Also: the shared JSON reader is used for automation-rule conditions, so the two
filter-shaped fields no longer disagree on whitespace; and stableStringify
answers for a BigInt rather than throwing, which it had promised not to do.
@rmyndharis
rmyndharis merged commit 48814e5 into main Aug 25, 2026
1 check passed
@rmyndharis
rmyndharis deleted the fix/pre-release-hardening branch August 25, 2026 05:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant