github rmyndharis/OpenWA v0.23.0

2 hours ago

Added

  • POST /sessions/{sessionId}/chats/read takes an optional messageIds array (up to 100) naming which messages to acknowledge. Baileys acknowledges individual messages, so without it a burst left its earlier messages unread. Ids resolve through the message store, so a group receipt carries its participant. Available on the agent tool and all five clients; ignored by whatsapp-web.js. Thanks @m7fz7.

Changed

  • The agent tools accept mentions on every send whose engine carries it (text, the four media sends, sticker, template and reply) and customLinkPreview on the text send, matching the REST routes. A tool schema is not strict, so an agent that passed either field before had it dropped without an error.
  • mentions reaches every route whose engine can carry it: reply, edit, send-template and each send-bulk item, alongside the send routes that already had it. send-template also gained linkPreview. On edit the tags are re-applied rather than preserved, because an edit replaces the message content. Thanks @Magnarks for the report.
  • POST /sessions/{sessionId}/chats/unread publishes its own MarkChatUnreadDto rather than sharing MarkChatReadDto. The body is unchanged (chatId alone), but a generated client sees the schema under a new name.
  • ⚠️ Breaking (Go, Java and typed Python callers). markRead and subscribePresence each take their own request type rather than the shared MarkChatRequest, which now serves markUnread alone. Go and Java need the swap at both call sites; typed Python only at markRead, its subscribePresence body being structurally identical. The wire body is unchanged, and JavaScript and PHP are unaffected.

Fixed

  • POST /messages/send-sticker applies the mentions it accepts. The route shares SendMediaMessageDto and docs/06 lists it among the media sends that take the field, but both adapters built the sticker content without a tag list, so a documented capability did nothing on either engine.

  • POST /chats/read answers 400 for "messageIds": null instead of 500. @IsOptional skips every validator for null as well as undefined, so the value reached the Baileys adapter and was dereferenced there. The published schema now carries minItems too, so it no longer advertises an empty array the server refuses.

  • A read receipt goes only to the chat the caller named. A message id belonging to another chat in the same session carried that chat's address out of the message store, so the receipt landed there while the route reported success for the chat in the path.

  • The Go client can express an empty messageIds again. omitempty on a plain slice dropped it, so a caller asking for nothing to be acknowledged silently acknowledged the newest message; the field is a pointer, so absent and empty are distinct on the wire.

  • The dashboard CSP nonce is substituted at every occurrence in the served document, not only the first. One placeholder exists today, so a second would have been left reading the literal text and its script refused by the browser.

  • Outbound webhook deliveries survive a hard crash. Fan-out was fire-and-forget, so a crash between persisting a message and completing its POST lost the delivery, against a documented at-least-once contract. Deliveries are now recorded before they are attempted, and a bounded sweep replays whatever is stranded under its stored idempotency key.

  • A stranded webhook delivery now gets the replay budget it was promised. The reconciler read success from a call that cannot fail, so a replay that never delivered was retired as dispatched on the first sweep and its payload dropped. Delivery reports an outcome instead, and a failed replay stays pending.

  • Restoring a backup no longer aborts when the target already holds the outbound delivery records. The table has no session foreign key, so the replace never cleared it and every overlapping row collided, rolling the whole import back.

  • Settled outbound delivery records are pruned after WEBHOOK_OUTBOX_RETENTION_DAYS (default 7). A record that can still be replayed is never pruned on age, and a non-positive window falls back to the default rather than letting the table grow without bound.

  • PLUGIN_STATE_DIR moves the plugin registry and per-plugin storage off the default ./data. It was the one piece of state with no path knob, so a test run rewrote the developer's own registry.

  • backup.sh and restore.sh follow PLUGIN_STATE_DIR. Both hardcoded the plugin state under the data dir, so with the knob set the archive carried neither the registry nor any plugin's persisted storage, and a restore put nothing back. The knob's own note now spells out which files to carry across when the knob changes.

  • The e2e lane sweeps the throwaway state roots it creates. Each suite gets its own, nothing removed them, and the temp directory accumulated hundreds of entries over a few days of runs.

  • e2e assertions are no longer answered by unrelated processes on the host. supertest binds its per-request listener to the wildcard address and then dials 127.0.0.1, which on macOS lets a process holding that port on 127.0.0.1 answer instead. Each suite's server now listens on loopback during init, which supertest reuses.

  • A stalled apt-get can no longer hold a CI run open. The scripts-smoke job installed sqlite3 and shellcheck unbounded, so a slow mirror held two main runs past an hour with every other job already green. Both steps now time out and skip the install when the runner already ships the tool.

Tests

  • The production HTTP stack is assembled by one configureApp that main.ts and the e2e suites both call, so the nonce, body caps, CORS and the SPA document handler are executed by tests instead of only in production.
  • The serve-static suite drops its own copy of the document handler, which omitted the nonce injection, and exercises the real one.

Don't miss a new OpenWA release

NewReleases is sending notifications on new releases.