Releases: 0xDxTz/WhatsMulti
Release list
v2.0.0
Promotes 2.0.0-rc.1 to stable. No code changes — same build, same behaviour.
Changed
- Published under the
latestdist-tag.npm install whatsmultinow resolves
without@next.
The v2 rewrite, complete. Built phase by phase against docs/REWRITE-v2-PLAN.md.
The package moved. v1 was published as @dutakey/whatsmulti; v2 is published as
whatsmulti, unscoped. npm has no redirect between two names, so upgrading is an
uninstall and an install — see MIGRATION.md §1. @dutakey/whatsmulti keeps working
and stays installable at 1.6.1; it simply receives no further releases. The scope named
a GitHub identity that no longer exists, and a breaking major is the only release where
renaming costs a user nothing they were not already paying.
A release candidate rather than a stable release, and it stays one until Baileys 7
ships stable or we have run an RC in production for long enough to vouch for it.
Published under the next dist-tag only, so it must be installed as
whatsmulti@next: a plain npm install whatsmulti has no latest to resolve until a
stable release exists.
Added
spec/— the language-neutral contract shared with the planned Go implementation
(whatsmulti-go, built on whatsmeow). Session state machine, canonical disconnect
causes, error codes, event names, config keys, REST contract, webhook envelope, and
the shared session-metadata and lock schema.spec/vectors/— fixtures every implementation is tested against. This is the
parity gate: a divergence between the TypeScript and Go builds becomes a red build
in whichever repo caused it.scripts/generate.mjs— compilesspec/*.yamlintosrc/generated/. Enums are
never hand-written in either runtime.scripts/generate-vectors.mjs— the reference implementation of the algorithms in
spec/algorithms.md.- Spec integrity test suite.
- Phase 1 foundation:
WhatsMultiErrorwith spec-driven codes, an injectable
Loggerinterface with a zero-dependency default, validated and frozen
configuration, a typed event bus, a plugin registry, andsrc/compat/baileys.ts
as the single Baileys touch point. - Phase 2 storage: a single
StorageAdaptercontract with required batch access, a
namespaced and exactly-invertible key layout, in-memory and filesystem backends,
and a shared conformance suite that every adapter must pass. - Phase 3 auth: one generic auth state built on
StorageAdapter, replacing v1's
three near-duplicate implementations; a Buffer-preserving value codec that is
byte-compatible with Baileys'BufferJSON; unambiguous Signal key naming; and lazy
driver loading, so importing the package does not pull Baileys into the module
graph and a missing peer reports the install command. - Phase 4 session core: a state machine compiled from the spec, a disconnect policy
driven by the canonical cause table, full-jitter reconnect backoff verified against
the shared vectors, a socket factory that is also the test seam, the session itself
(QR lifecycle, pairing codes, logout separated from local delete), and a manager
with a namespace-derived registry, bounded loading and a real shutdown. - Phase 5 messaging: a bounded, rate-limited per-session send queue, a send path with
a deadline and typed failures, and a media downloader that can refresh an expired
media URL instead of failing permanently. - Phase 6 facade: the
WhatsMulticlient — one instance-owned config, logger, event
bus, plugin registry and session manager — pluswhatsmulti/qr, a second
entry point that renders a QR to the terminal, SVG or PNG behind the optional
qrcodepeer. Baileys 7 removedprintQRInTerminal;qr.printreplaces it. - Phase 7 cluster: a
LockProvidercontract whose row shape is the one in
spec/storage-schema.sql, so a Go instance and a TypeScript instance sharing a
database fence each other; an in-process provider as the default; and fail-stop
fencing inSession— the lock is taken before the socket opens, renewed on a
heartbeat, and losing it closes the socket at once and emitssession.fenced. - Phase 8 adapters:
whatsmulti/mongo,/redisand/sql, each a storage
backend and a lock provider on its own subpath, so installing the package never
pulls in a database driver. The SQL one covers PostgreSQL, MySQL and SQLite through
Drizzle. All five storage backends and all four lock providers run the shared
conformance suites green. test/conformance/lock.ts— the lock counterpart to the storage conformance suite.
A provider, including a third-party one, is finished when both are green.LOGOUT_FAILED,SESSION_FAILEDandMEDIA_DOWNLOAD_FAILEDerror codes, a
{detail}slot onSEND_FAILED, and JID/phone normalisation
matching whatsmeow's PairPhone validation.- Phase 9 webhook:
whatsmulti/webhook, an HMAC-SHA256 signed event
forwarder with a batching window, a bounded queue, dead-lettering and retries on the
same full-jitter schedule as reconnects. Deliveries are posted one at a time and in
order — parallel posts with independent retries would routinely show a receiver
session.state openbefore theqrthat preceded it — and a retry re-sends
identical bytes under the original timestamp, so a receiver can verify and
deduplicate. The envelope, the signing recipe and the verification steps are
specified inspec/webhook.md. - Phase 10 server:
whatsmulti/server, a REST + SSE control plane on Hono
behind bearer authentication, with/healthzand Prometheus/metrics. Serving
without a token takes an explicitinsecure: true, and passing both is refused
rather than resolved. The HTTP status per error code lives inspec/errors.yaml
rather than in the server, so an API client branching on 409 versus 422 never has to
ask which runtime it is talking to. Contract tests validate real responses against
spec/openapi.yaml. spec/metrics.md— the metric names and label rules, so a dashboard built against
one runtime does not break on the other.INVALID_REQUEST,UNAUTHORIZED,ROUTE_NOT_FOUNDandINTERNAL_ERRORerror
codes, and anhttpstatus on every code.MIGRATION.md, a rewritten README, seven runnable examples underexamples/, and a
typedoc API reference (npm run docs). The examples are type checked in CI: an
example that no longer compiles is a documentation bug.- CI: spec-drift gate, Node 20/22/24 matrix, a Bun job,
publint+attwpackage
shape validation, a typedoc validation run, and a daily job that runs the suite
against the current Baileys release.
Changed
- Breaking: ESM-only, Node >= 20.
require()is no longer supported. - Breaking: targets the Baileys
7.0.0-rcline. v7 introduces the LID identity
system and three new auth key types (lid-mapping,device-list,tctoken) that
a v6-shaped auth state cannot persist. The v6 line is now taggedlegacyupstream. - Baileys is a peer dependency rather than a direct dependency, so consumers own the
version. - Core has zero runtime dependencies.
mongoose,pino,pino-pretty,qrcode
and@hapi/boomare gone; the storage, logging and QR backends they served become
optional peers behind subpath exports. tscin strict mode withnodenextresolution, replacing astrict: falsebuild.- Release is manual and tag-driven, with a workflow that refuses to publish when the
git tag andpackage.jsonversion disagree, and routes prereleases to thenext
dist-tag instead oflatest.
Fixed
-
Reconnect covers every disconnect cause, with backoff and a credential purge on
logout. v1 reconnected only onrestartRequired, immediately and forever, and never
purged -- so a device unlinked from the phone became an infinite reconnect loop
against credentials that could never work again. -
Starting a session twice is refused by construction rather than by a check. v1
needed a dedicated commit to patch that race. -
deleteSessionno longer unlinks the device: local removal and logout are separate
operations, and a failed unlink keeps the credentials so it can be retried. -
qrTimeoutMsandmaxQrAttemptsare read. v1 declared them in its types and never
used them. -
loadSessionsfans out with a bounded pool instead of an unboundedPromise.all. -
Session listing is derived from the storage namespace, replacing a filesystem scan
whose Mongo counterpart used an async predicate inside.filter()and therefore
never filtered anything. -
Sends are serialised per session. Two in flight at once mutate the same Signal
session state concurrently, and the loser produces a message the recipient cannot
decrypt. v1 sent straight from the caller's stack, with no queue and no rate limit. -
The send queue is bounded and refuses work when full, instead of growing until the
process runs out of memory while the caller sees nothing wrong. -
Media downloads pass a re-upload request to the driver, so an expired media URL is
refreshed rather than becoming a permanent failure. -
There is a shutdown path:
destroy()stops every session and closes every adapter,
continuing past individual failures. -
Signal keys are read and written in batches. v1 issued one storage round trip per
key, and Baileys asks for thirty or more while resuming a session. -
Stored key names parse correctly. v1 split
<type>-<id>with a lazy regex, so
pre-key-42parsed as typeprewith idkey-42, and every key type except
sessionandtctokencame out wrong. -
app-state-sync-keytimestamps are revived as protobufLongs rather than left as
the numbers JSON returns, which app-state key rotation compares against. -
Resetting a corrupt Signal key store no longer removes credentials, so it no longer
unlinks the device. -
The
LICENSEfile now matches the licence this package has always been published
under. Every release since 1.1.0 declaredMITin its npm metadata while the
...
v2.0.0-rc.1
The v2 rewrite, complete. Built phase by phase against docs/REWRITE-v2-PLAN.md.
The package moved. v1 was published as @dutakey/whatsmulti; v2 is published as
whatsmulti, unscoped. npm has no redirect between two names, so upgrading is an
uninstall and an install — see MIGRATION.md §1. @dutakey/whatsmulti keeps working
and stays installable at 1.6.1; it simply receives no further releases. The scope named
a GitHub identity that no longer exists, and a breaking major is the only release where
renaming costs a user nothing they were not already paying.
A release candidate rather than a stable release, and it stays one until Baileys 7
ships stable or we have run an RC in production for long enough to vouch for it.
Published under the next dist-tag only, so it must be installed as
whatsmulti@next: a plain npm install whatsmulti has no latest to resolve until a
stable release exists.
Added
spec/— the language-neutral contract shared with the planned Go implementation
(whatsmulti-go, built on whatsmeow). Session state machine, canonical disconnect
causes, error codes, event names, config keys, REST contract, webhook envelope, and
the shared session-metadata and lock schema.spec/vectors/— fixtures every implementation is tested against. This is the
parity gate: a divergence between the TypeScript and Go builds becomes a red build
in whichever repo caused it.scripts/generate.mjs— compilesspec/*.yamlintosrc/generated/. Enums are
never hand-written in either runtime.scripts/generate-vectors.mjs— the reference implementation of the algorithms in
spec/algorithms.md.- Spec integrity test suite.
- Phase 1 foundation:
WhatsMultiErrorwith spec-driven codes, an injectable
Loggerinterface with a zero-dependency default, validated and frozen
configuration, a typed event bus, a plugin registry, andsrc/compat/baileys.ts
as the single Baileys touch point. - Phase 2 storage: a single
StorageAdaptercontract with required batch access, a
namespaced and exactly-invertible key layout, in-memory and filesystem backends,
and a shared conformance suite that every adapter must pass. - Phase 3 auth: one generic auth state built on
StorageAdapter, replacing v1's
three near-duplicate implementations; a Buffer-preserving value codec that is
byte-compatible with Baileys'BufferJSON; unambiguous Signal key naming; and lazy
driver loading, so importing the package does not pull Baileys into the module
graph and a missing peer reports the install command. - Phase 4 session core: a state machine compiled from the spec, a disconnect policy
driven by the canonical cause table, full-jitter reconnect backoff verified against
the shared vectors, a socket factory that is also the test seam, the session itself
(QR lifecycle, pairing codes, logout separated from local delete), and a manager
with a namespace-derived registry, bounded loading and a real shutdown. - Phase 5 messaging: a bounded, rate-limited per-session send queue, a send path with
a deadline and typed failures, and a media downloader that can refresh an expired
media URL instead of failing permanently. - Phase 6 facade: the
WhatsMulticlient — one instance-owned config, logger, event
bus, plugin registry and session manager — pluswhatsmulti/qr, a second
entry point that renders a QR to the terminal, SVG or PNG behind the optional
qrcodepeer. Baileys 7 removedprintQRInTerminal;qr.printreplaces it. - Phase 7 cluster: a
LockProvidercontract whose row shape is the one in
spec/storage-schema.sql, so a Go instance and a TypeScript instance sharing a
database fence each other; an in-process provider as the default; and fail-stop
fencing inSession— the lock is taken before the socket opens, renewed on a
heartbeat, and losing it closes the socket at once and emitssession.fenced. - Phase 8 adapters:
whatsmulti/mongo,/redisand/sql, each a storage
backend and a lock provider on its own subpath, so installing the package never
pulls in a database driver. The SQL one covers PostgreSQL, MySQL and SQLite through
Drizzle. All five storage backends and all four lock providers run the shared
conformance suites green. test/conformance/lock.ts— the lock counterpart to the storage conformance suite.
A provider, including a third-party one, is finished when both are green.LOGOUT_FAILED,SESSION_FAILEDandMEDIA_DOWNLOAD_FAILEDerror codes, a
{detail}slot onSEND_FAILED, and JID/phone normalisation
matching whatsmeow's PairPhone validation.- Phase 9 webhook:
whatsmulti/webhook, an HMAC-SHA256 signed event
forwarder with a batching window, a bounded queue, dead-lettering and retries on the
same full-jitter schedule as reconnects. Deliveries are posted one at a time and in
order — parallel posts with independent retries would routinely show a receiver
session.state openbefore theqrthat preceded it — and a retry re-sends
identical bytes under the original timestamp, so a receiver can verify and
deduplicate. The envelope, the signing recipe and the verification steps are
specified inspec/webhook.md. - Phase 10 server:
whatsmulti/server, a REST + SSE control plane on Hono
behind bearer authentication, with/healthzand Prometheus/metrics. Serving
without a token takes an explicitinsecure: true, and passing both is refused
rather than resolved. The HTTP status per error code lives inspec/errors.yaml
rather than in the server, so an API client branching on 409 versus 422 never has to
ask which runtime it is talking to. Contract tests validate real responses against
spec/openapi.yaml. spec/metrics.md— the metric names and label rules, so a dashboard built against
one runtime does not break on the other.INVALID_REQUEST,UNAUTHORIZED,ROUTE_NOT_FOUNDandINTERNAL_ERRORerror
codes, and anhttpstatus on every code.MIGRATION.md, a rewritten README, seven runnable examples underexamples/, and a
typedoc API reference (npm run docs). The examples are type checked in CI: an
example that no longer compiles is a documentation bug.- CI: spec-drift gate, Node 20/22/24 matrix, a Bun job,
publint+attwpackage
shape validation, a typedoc validation run, and a daily job that runs the suite
against the current Baileys release.
Changed
- Breaking: ESM-only, Node >= 20.
require()is no longer supported. - Breaking: targets the Baileys
7.0.0-rcline. v7 introduces the LID identity
system and three new auth key types (lid-mapping,device-list,tctoken) that
a v6-shaped auth state cannot persist. The v6 line is now taggedlegacyupstream. - Baileys is a peer dependency rather than a direct dependency, so consumers own the
version. - Core has zero runtime dependencies.
mongoose,pino,pino-pretty,qrcode
and@hapi/boomare gone; the storage, logging and QR backends they served become
optional peers behind subpath exports. tscin strict mode withnodenextresolution, replacing astrict: falsebuild.- Release is manual and tag-driven, with a workflow that refuses to publish when the
git tag andpackage.jsonversion disagree, and routes prereleases to thenext
dist-tag instead oflatest.
Fixed
-
Reconnect covers every disconnect cause, with backoff and a credential purge on
logout. v1 reconnected only onrestartRequired, immediately and forever, and never
purged -- so a device unlinked from the phone became an infinite reconnect loop
against credentials that could never work again. -
Starting a session twice is refused by construction rather than by a check. v1
needed a dedicated commit to patch that race. -
deleteSessionno longer unlinks the device: local removal and logout are separate
operations, and a failed unlink keeps the credentials so it can be retried. -
qrTimeoutMsandmaxQrAttemptsare read. v1 declared them in its types and never
used them. -
loadSessionsfans out with a bounded pool instead of an unboundedPromise.all. -
Session listing is derived from the storage namespace, replacing a filesystem scan
whose Mongo counterpart used an async predicate inside.filter()and therefore
never filtered anything. -
Sends are serialised per session. Two in flight at once mutate the same Signal
session state concurrently, and the loser produces a message the recipient cannot
decrypt. v1 sent straight from the caller's stack, with no queue and no rate limit. -
The send queue is bounded and refuses work when full, instead of growing until the
process runs out of memory while the caller sees nothing wrong. -
Media downloads pass a re-upload request to the driver, so an expired media URL is
refreshed rather than becoming a permanent failure. -
There is a shutdown path:
destroy()stops every session and closes every adapter,
continuing past individual failures. -
Signal keys are read and written in batches. v1 issued one storage round trip per
key, and Baileys asks for thirty or more while resuming a session. -
Stored key names parse correctly. v1 split
<type>-<id>with a lazy regex, so
pre-key-42parsed as typeprewith idkey-42, and every key type except
sessionandtctokencame out wrong. -
app-state-sync-keytimestamps are revived as protobufLongs rather than left as
the numbers JSON returns, which app-state key rotation compares against. -
Resetting a corrupt Signal key store no longer removes credentials, so it no longer
unlinks the device. -
The
LICENSEfile now matches the licence this package has always been published
under. Every release since 1.1.0 declaredMITin its npm metadata while the
repository carried the GPL-3.0 text, which is a contradiction a user could only
resolve by guessing. MIT is the grant that was actually made to everyone who
installed it, and it is now the...
v1.6.1
v1.6.0
v1.5.3
v1.5.2
v1.5.1
v1.5.0
1.5.0 (2025-07-17)
Bug Fixes
- readme: example folder path in README (47a3789)
- release: include missing commits from dev in release (f38a433)
- session: loop event (c2af051)
Features
- authState: setMeta, getMeta (e39c432)
- config: BaileysLoggerLevel config option (904d5af)
- event: process callback to WMEventEmitter (179868d)
- event: process function return event, eventKey (d72d778)
- events: create WMEventEmitter class for centralized event handling (ffd7462)
- logger: add logger prefix (d379ca5)
- mongo: AuthModel (80dc70c)