v0.4.0
What's New in v0.4.0
This is a major release with 70+ PRs since v0.3.0, bringing runtime flexibility, new feature areas, significant performance work, and many protocol fixes.
Highlights
- Runtime-agnostic core (#393) — Tokio is no longer a hard dependency. A new
Runtimetrait allows plugging in custom runtimes (useful for WASM, embedded, or alternative async runtimes).BotBuildernow uses a typestate pattern requiring.with_runtime(). - Newsletter (Channel) support (#390) — Full CRUD: create, join, leave, update, list, get metadata, send messages, reactions, paginated history, and live update events.
- Community support (#392) — Create/deactivate communities, link/unlink subgroups, query metadata, fetch participants. MEX (GraphQL) queries included.
- Group invites, membership requests & privacy (#396) — Join via invite link (with approval handling), approve/reject membership requests, set member add mode, individual privacy settings, default disappearing messages, business profile queries.
- Pluggable cache backends (#381) — New
CacheStoretrait lets you plug in Redis, Memcached, or any custom cache backend per cache type. - Bundled SQLite (#364) —
bundled-sqlitefeature (default-on) eliminates the need for system-level SQLite. - Media resilience (#361, #356) — Host failover, resumable uploads (≥5 MiB), download URL re-derivation on 404/410, and automatic retry on 401/403 with auth refresh.
- DB-backed sent message retry (#360) — Sent messages stored in SQLite instead of memory. RSS baseline dropped from 93.9 MiB to 37.9 MiB.
- Configurable caches (#334) — All cache TTLs and capacities are now configurable via
CacheConfig. - Stable Rust support (#314, #319) —
portable_simdis opt-out via thesimdfeature flag. Allunsaferemoved from the binary decoder.
Performance
Massive allocation reduction across hot paths (#375–#380, #382, #385, #387):
Cow<'static, str>forJid.server,Node.tag, andAttrskeys- Granular cache patching instead of invalidate+refetch
- Single-allocation protocol address strings
- History sync streaming and RAM optimization (#362)
- Pre-allocated collections throughout
Connection Reliability
- WhatsApp Web-matching keepalive (15s ping, 20s dead socket, Fibonacci backoff) (#341)
- Stream error handling: 401 (logged out), 409 (stream replaced), 429 (rate limited) (#353)
- 20s transport connect timeout (#366)
- AtomicBool
is_connected()preventing silent ack drops (#383) - Deterministic message ordering replacing 500ms re-queue hack (#328)
New Events
GroupUpdate(replacesGroupInfoUpdate) — granular per-action group notificationsContactUpdated,ContactNumberChanged,ContactSyncRequestedDisappearingModeChanged,NewsletterLiveUpdatePictureUpdate(updated fields),StarUpdate,PushNameUpdate,SelfPushNameUpdatedStreamReplaced,BusinessStatusUpdate,DeviceListUpdate
Bug Fixes
- Prekey ID collision causing ~5.7% message decryption failures during offline sync (#322)
- DeviceSentMessage not unwrapped before dispatch — self-sent messages appeared empty (#337)
- AD_JID binary encoding writing incorrect
domain_type(#391) - App state race condition in storage (#332)
- Ack type attribute misalignment causing server disconnections (#365)
⚠️ Breaking Changes
BotBuilder requires .with_runtime() (#393)
// Before (v0.3)
let bot = BotBuilder::new()
.with_backend(backend)
.with_transport(transport)
.with_http_client(http)
.build(handler);
// After (v0.4)
use whatsapp_rust::TokioRuntime;
let bot = BotBuilder::new()
.with_backend(backend)
.with_transport(transport)
.with_http_client(http)
.with_runtime(TokioRuntime) // NEW — required
.build(handler);Bot::run() returns BotHandle instead of JoinHandle (#393)
// Before
let join_handle: tokio::task::JoinHandle<()> = bot.run().await?;
join_handle.await?;
// After
let handle: BotHandle = bot.run().await?;
handle.wait().await; // dropping BotHandle aborts the taskJid.server is now Cow<'static, str> (#376)
Code directly accessing jid.server as String needs updating. .to_string() or .as_ref() will work.
NodeBuilder::new() requires &'static str (#385)
Use NodeBuilder::new_dynamic(string) for runtime-generated tags.
NodeValue API simplified (#386)
Removed: as_jid(), to_string_value(), string().
Use: as_str() -> Cow<str>, to_jid() -> Option<Jid>, or PartialEq<str> comparisons.
ProtocolStore trait has new required methods (#360)
If you implement ProtocolStore yourself (custom storage backend), add:
store_sent_message()take_sent_message()delete_expired_sent_messages()
GroupInfoUpdate event replaced by GroupUpdate (#327)
Pattern match on Event::GroupUpdate instead of Event::GroupInfoUpdate.
PictureUpdate event fields changed (#316)
Fields are now: author: Option<Jid>, removed: bool, picture_id: Option<String>.
What's Changed (auto generated)
- test: add tests e2e with bartender by @jlucaso1 in #303
- Feat improve group tests by @jlucaso1 in #304
- feat: add set name and status by @jlucaso1 in #305
- feat: node waiter by @jlucaso1 in #307
- test: add upload and download e2e tests by @jlucaso1 in #309
- chore(deps): bump ghash from 0.5.1 to 0.6.0 by @dependabot[bot] in #308
- fix(wacore): use Vec in key_pair_serde::deserialize to fix JSON roundtrip by @Devansh-bit in #311
- feat: action state by @jlucaso1 in #310
- feat: signal cache by @jlucaso1 in #312
- feat(wacore-binary): stable Rust support with scalar SIMD fallback by @adolfousier in #314
- test: e2e offline events testing by @jlucaso1 in #313
- feat: receipts e2e tests by @jlucaso1 in #315
- feat: profile complete by @jlucaso1 in #316
- Tests split to improve paralelism by @jlucaso1 in #317
- refactor: improve macro derive by @jlucaso1 in #318
- feat: SIMD opt-out, remove all unsafe from decoder, fix LTHash endianness by @jlucaso1 in #319
- feat: improve notification status handling by @jlucaso1 in #320
- fix: improve disconnection handling by @jlucaso1 in #321
- fix: prekey ID collision and signal cache bypass causing decryption failures by @jlucaso1 in #322
- Fix more improvements decrypt by @jlucaso1 in #323
- refactor: improve error typesafety by @jlucaso1 in #325
- Chore increase prekeys by @jlucaso1 in #324
- perf: bound cache by @jlucaso1 in #326
- feat: add more events by @jlucaso1 in #327
- fix: replace 500ms re-queue hack with deterministic message ordering by @jlucaso1 in #328
- Add Claude Code GitHub Workflow by @jlucaso1 in #329
- test: improve e2e test by @jlucaso1 in #331
- fix: app state race storage by @jlucaso1 in #332
- fix: handle different type of ping by @jlucaso1 in #333
- feat: add configurable cache TTL and capacity via CacheConfig by @jlucaso1 in #334
- fix: signal keepalive loop shutdown on connection cleanup by @jlucaso1 in #335
- fix: unwrap DeviceSentMessage before dispatch by @jlucaso1 in #337
- feat: merge messageContextInfo when unwrapping DeviceSentMessage by @jlucaso1 in #339
- fix: keepalive timeout detection and pong id handling by @jlucaso1 in #340
- feat: match WhatsApp Web keepalive, dead socket, and reconnect behavior by @jlucaso1 in #341
- feat: handle stream error codes 401, 409, 429 by @jlucaso1 in #353
- feat: handle disappearing_mode notification type by @jlucaso1 in #354
- Handle
contactsnotifications for contact updates, number changes, and sync requests by @Copilot in #358 - Retry media upload/download on 401/403 with forced media auth refresh by @Copilot in #356
- Add presence unsubscribe API and re-subscribe tracked contacts on reconnect by @Copilot in #355
- Add offline sync timeout fallback and offline sequence validation by @Copilot in #357
- feat: DB-backed sent message retry + configurable cache reductions by @jlucaso1 in #360
- feat: media host failover, resumable upload, download URL re-derivation by @jlucaso1 in #361
- perf: history sync RAM optimization + fix protocol mismatches causing disconnects by @jlucaso1 in #362
- fix: remove unecessary offline sequence by @jlucaso1 in #363
- feat: bundled sqlite by @jlucaso1 in #364
- fix: align ack type attribute handling with whatsmeow/WA Web by @jlucaso1 in #365
- fix: add 20s transport connect timeout matching WA Web by @jlucaso1 in #366
- feat: handle enc_rekey_retry receipt type for VoIP call re-keying by @jlucaso1 in #371
- feat: add version rollback protection to app state sync by @jlucaso1 in #374
- chore(deps): bump once_cell from 1.21.3 to 1.21.4 by @dependabot[bot] in #373
- chore(deps): bump diesel from 2.3.6 to 2.3.7 by @dependabot[bot] in #372
- perf: reduce heap allocations in signal cache and server_jid by @jlucaso1 in #375
- perf: Cow<'static, str> for Jid server and hot-path optimizations by @jlucaso1 in #376
- perf: eliminate allocations in app state sync and pre-allocate collections by @jlucaso1 in #377
- perf: reduce allocations in history sync decrypt and LazyConversation by @jlucaso1 in #378
- perf: use NodeBuilder chaining and jid_attr in send/receipt hot paths by @jlucaso1 in #379
- perf: single-allocation to_protocol_address_string() for session lock keys by @jlucaso1 in #380
- feat: pluggable cache store adapter for custom backends by @jlucaso1 in #381
- perf: granular cache patching instead of invalidate+refetch by @jlucaso1 in #382
- fix: replace try_lock() in is_connected() with AtomicBool to prevent silent ack drops by @jlucaso1 in #383
- perf: remove unnecessary locks identified by WA Web comparison by @jlucaso1 in #384
- perf: use Cow<'static, str> for Node tags and Attrs keys by @jlucaso1 in #385
- refactor: simplify NodeValue API to 2 methods, fix AttrParser JID bug by @jlucaso1 in #386
- perf: reduce hot path allocations on send and receive paths by @jlucaso1 in #387
- feat: newsletter (channel) support — full CRUD, messaging, reactions, live updates by @jlucaso1 in #390
- fix: write correct domain_type in AD_JID binary encoding by @jlucaso1 in #391
- feat: community support — full CRUD, subgroup management, queries by @jlucaso1 in #392
- refactor: runtime agnostic by @jlucaso1 in #393
- chore: update rand deps and dedupe by @jlucaso1 in #394
- feat: group invite, membership requests, privacy, business profiles & fixes by @jlucaso1 in #396
New Contributors
- @Devansh-bit made their first contribution in #311
- @adolfousier made their first contribution in #314
Full Changelog: v0.3.0...v0.4.0