Skip to content

v0.17.0

Latest

Choose a tag to compare

@rmyndharis rmyndharis released this 12 Aug 09:22
· 1 commit to main since this release

Added

  • Turkish (tr) dashboard translation. Thanks @codedByCan.
  • ⚠️ Breaking (config). AUDIT_RETENTION_DAYS is checked at boot, so a typo fails startup with a named error instead of silently reverting to the 90-day default. It is validated as an integer of any sign rather than a positive one, because both 0 and any negative value are documented switches that disable audit-log pruning entirely. Action required: a value that is not a plain integer now refuses to boot where it previously started. 30d and 90.5 were accepted before and quietly became 30 and 90; +90 became 90. Set a plain integer — 90 — or leave the knob unset.
  • message.controller.spec.ts holds the two headers the stored-media download sends, X-Content-Type-Options: nosniff and Content-Disposition: attachment; deleting either had passed every suite in the repo.
  • That same spec now also holds the passthrough declaration and the returned bytes, so a media response carrying both security headers and no body at all no longer passes it.
  • Optional quotedMessageId on the nine send-* message endpoints that carry a single message — text, image, video, audio, document, sticker, location, contact and poll — and on their MCP tools, so a reply can carry media, a location, a contact card or a poll instead of only text. send-template, send-bulk, send-product and send-catalog do not accept it. An id the engine cannot resolve now fails the send rather than delivering the message unquoted. Thanks @nirizr for the report.
  • All five SDK clients expose quotedMessageId on their send request types, so a typed caller can reply with media, a location, a contact card or a poll without hand-building the body.

Changed

  • ⚠️ Breaking (plugins). Plugins must declare a storage:use permission to reach ctx.storage, and an installed plugin that persists state needs that one manifest line added before it can store again. Action required: upgrade the official plugins to the versions listed below BEFORE upgrading the gateway, or add the permission to any first-party plugin's manifest.json. The four storage verbs dispatched with no permission check at all, so a plugin whose manifest declared nothing still wrote to the host disk and the operator reading that manifest could not see it. The per-plugin directory, key check and byte quota already bounded the access, so this puts a plugin's use of host storage in the manifest rather than closing an escape.
  • Seven official plugins declare it as of chatwoot-adapter 0.9.1, chat-flow 1.1.2, group-translate 1.3.1, gsheets-logger 0.3.3, http-action 0.2.2, typebot-connector 0.2.2 and voice-transcription 1.2.3; upgrade to at least these before upgrading the gateway, since a plugin below them is denied at its next storage call rather than at load — and one that stores during onEnable is left in ERROR by that denial, not merely refused the write. after-hours, faq-bot and supabase-otp-hook never touch ctx.storage and need no upgrade.

Fixed

  • A plugin denied a capability was told which permission it lacked but not where to declare it. Both refusals — the capability denial, which fires when a verb is called and so reaches the operator detached from whatever upgrade caused it, and the load-time ingress check — now name the permissions array and the plugin's manifest.json. A spec binds the quotation in docs/19 to the string the code throws, so the two cannot drift apart again.
  • The dashboard bundled all thirteen locales into one 476 KB chunk the page preloaded, so every visitor downloaded twelve languages to read one; each is now fetched on demand.
  • Fifteen return and parameter annotations in the Python SDK named list[...] inside classes that define a list method, so each resolved to the method rather than the builtin. That package ships py.typed, so the wrong types were what a consumer's own type checker read. Its CI now runs mypy, which nothing did before.
  • A locale chunk that failed to load left the dashboard right-to-left around English copy, because text direction followed the requested language rather than the catalogue that answered; it now follows what actually rendered.
  • check:sdk-routes scanned the JavaScript client for backtick-delimited paths only, while its PHP and Python rules already accepted every quote style, so the nine routes that client writes as single-quoted strings were never compared to the contract — /api/health/ready among them, which is also the container healthcheck and Kubernetes readiness path. All nine are written by the PHP and Python clients too, and those rules did accept every quote style, so a server-side rename still reddened this gate through them and check:sdk-coverage — the JavaScript client simply had no cover of its own, and would have been the one shipping a stale path.
  • The JavaScript, Python, Go and Java SDKs omitted contact, call and ephemeralDuration from their chat-history message type, so a typed client had to cast to read three fields the endpoint returns; all four now mirror the engine payload.
  • POST /api/infra/import-data bound its body to an inline type, which erases at runtime, so on the replace-all restore the global ValidationPipe's whitelist and forbidNonWhitelisted never ran: a body carrying no tables failed as a 500 from inside the import, and a misspelled key was accepted in silence. It now takes a DTO — a missing tables answers 400 instead, naming the field outside production, where field-level detail is suppressed unless VALIDATION_ERROR_DETAIL=true; an unknown key is refused, and force/stopOrphans accept only a real boolean or an exact 'true'/'false', so an ambiguous spelling cannot open the orphan-engine escape. The export file still posts back verbatim: the five metadata fields it wraps tables in are accepted and ignored, and a spec probes every field the export publishes so a new one cannot start failing the restore.
  • The engine parity gate took an adapter file's engine from its filename prefix and credited the rest to whatsapp-web.js, so a 501 added to four of the five unprefixed modules would be blamed on the wrong engine — and the invariant would then demand that the engine which really refuses be marked supported. Those files are now attributed explicitly, and one that is neither prefixed nor attributed fails the spec by name. The three modules both adapters import are marked shared rather than attributed, because no attribution is correct for them: crediting both engines demands not-available from the one that never refuses, crediting neither demands supported from the one that does. A refusal added to a shared module is now refused by name, pointing at the engine delegate it belongs in.
  • Replying with an attachment in the dashboard composer silently dropped the quote: the media branch never carried the quoted id, the optimistic bubble's metadata took the same either/or, and the reply banner was cleared regardless — so it looked exactly like a quoted reply had been sent. Both payloads now carry the quote, and the two builders moved to a tested utility module.
  • An unresolvable quotedMessageId answered 500 on whatsapp-web.js where Baileys answered 404 for the identical request, and the bare page error also counted against the send breaker, which exempts a 4xx. Both engines now answer 404.
  • The Java SDK omitted quotedMessageId from its send-audio request, the one send whose model is a separate record rather than the shared media one, so send-audio was the single quotable route a Java caller could not quote on.
  • The spec binding docs/19 to the plugin denial message covered only the sentence that names the fix, leaving the sentence that names the fault free to be reworded with the document still asserting the old text.
  • The chat-media backlog test timed out on every full-suite run while passing whenever its file ran alone, reporting itself as a product failure; it now carries a timeout matching the work it does.

Documentation

  • .env.example was missing six knobs the app reads at runtime — DOCKER_HOST, BAILEYS_MESSAGE_STORE_LIMIT, STORAGE_LIST_MAX_FILES, PLUGIN_CATALOG_URL, BOOTSTRAP_KEY_FILE and VALIDATION_ERROR_DETAIL — none of which any of the three key lists the completeness gate reads happens to enumerate, so the gate could not have caught them. All six are commented out, so copying the file still pins nothing.
  • docs/06-api-specification.md restated the published contract by hand with nothing binding the two, so operations shipped without ever reaching it — the integration redrive route was documented nowhere at all. It now has a section, and a new spec compares the document's route headings to openapi.json in both directions; an operation deliberately documented elsewhere carries an allowlist entry naming that file, and the gate reads the file to confirm the route is really there.
  • .env.example calls itself the single source of truth for configuration, and the Helm chart and docs/10 both defer to it, but seven live operator knobs were missing from it — including SERVE_DASHBOARD, which the bundled compose forwards explicitly, so an operator wanting an API-only deployment was told the capability did not exist. A new spec binds the file to the key lists env.validation.ts, env-precedence.ts and compose already maintain, so the next one cannot go missing quietly.
  • RESOLVE_LID_TO_PHONE was documented nowhere outside .env.example, so an operator receiving @lid senders had no path to the flag that resolves them; the event catalog now carries the senderPhone opt-in callout, the contact-phone endpoint points back to it, and the troubleshooting FAQ covers the symptom.
  • The chat-history response example advertised a senderPhone field that endpoint has never returned, and showed it on a plain @c.us sender that not even the inbound path would resolve; the example now matches what the route emits, and points at the contact-phone endpoint instead.
  • WEBHOOK_CONTACT_DETAILS was absent from docs/; the event catalog now names the twelve fields it adds to contact on message.received, notes that opting in costs no extra WhatsApp lookup, and records that only the whatsapp-web.js path reads the flag — which .env.example now says too.
  • The chat-history field list omitted ephemeralDuration and its type union omitted poll, both of which that route emits; [Unreleased] also carried two ### Fixed headings after two branches added one each without conflicting.
  • The webhook secret and headers were described as never returned by any API response in six places across docs/04, docs/06, docs/07, the response DTO and the webhook e2e suite, while GET /api/infra/export-data dumps the rows verbatim and returns both in cleartext — which docs/06 also said, two sections further down. Every one of them now bounds the claim to the webhook routes and names the export route as the exception, the way the neighbouring proxy-credential row already scopes its claim to the session read DTOs.