@dojocoding/whatsapp-sdk 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,402 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@dojocoding/whatsapp-sdk` (formerly
4
+ `@dojocoding/whatsapp`) are documented in this file.
5
+
6
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
7
+ and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
+ Pre-1.0 minor versions may contain breaking changes — see
9
+ [`CONTRIBUTING.md`](../../CONTRIBUTING.md) § Releases.
10
+
11
+ ## [0.8.0] — 2026-05-10
12
+
13
+ ### Renamed: `@dojocoding/whatsapp` → `@dojocoding/whatsapp-sdk`
14
+
15
+ This release renames the package to match the new two-package
16
+ architecture (`@dojocoding/whatsapp-sdk` + the new sibling
17
+ [`@dojocoding/whatsapp-mcp`](../whatsapp-mcp/CHANGELOG.md), which
18
+ exposes the SDK's outbound surface as a Model Context Protocol
19
+ server for LLM agents).
20
+
21
+ **Zero runtime change.** The 572-test suite passes verbatim
22
+ against `0.7.4`'s public surface. No symbol renames, no type
23
+ changes, no behaviour change.
24
+
25
+ **Migration — one line of `package.json`:**
26
+
27
+ ```diff
28
+ "dependencies": {
29
+ - "@dojocoding/whatsapp": "^0.7.0"
30
+ + "@dojocoding/whatsapp-sdk": "^0.8.0"
31
+ }
32
+ ```
33
+
34
+ …plus a project-wide find-and-replace on import statements:
35
+
36
+ ```diff
37
+ - import { WhatsAppClient } from "@dojocoding/whatsapp";
38
+ + import { WhatsAppClient } from "@dojocoding/whatsapp-sdk";
39
+ ```
40
+
41
+ …and equivalent updates for subpath imports (`/express`,
42
+ `/web`, `/hono`, `/storage/redis`, `/storage/postgres`).
43
+
44
+ The old `@dojocoding/whatsapp` package is `npm deprecate`-d with
45
+ a redirect message; the 13 published versions (0.1.0–0.7.4)
46
+ stay installable for pinned consumers.
47
+
48
+ ### Repo structure (no consumer-visible change)
49
+
50
+ - The repo is now a `pnpm` workspace. SDK code lives under
51
+ `packages/whatsapp-sdk/`; the new MCP server lives under
52
+ `packages/whatsapp-mcp/`.
53
+ - Tag prefixes disambiguate release targets in CI:
54
+ `sdk-v0.x.x` for this package, `mcp-v0.x.x` for the MCP
55
+ sibling. The legacy `v0.x.x` prefix retires with this
56
+ release.
57
+ - Docs reorganise under repo-root `docs/` with `sdk/`, `mcp/`,
58
+ and `cookbook/{sdk,mcp,hybrid}/` subtrees. The
59
+ [`hybrid/`](../../docs/cookbook/hybrid/) cookbook (lands in
60
+ Phase C3) is the showcase for using both packages together.
61
+
62
+ See OpenSpec change
63
+ [`2026-05-10-add-mcp-server`](../../openspec/changes/2026-05-10-add-mcp-server/)
64
+ for the full rationale.
65
+
66
+ ## [0.7.4] — 2026-05-10
67
+
68
+ ### Tooling (no SDK behaviour change)
69
+
70
+ Phase 4 of the Track F + test-coverage audit hardening pass. The
71
+ published artefact is functionally identical to 0.7.3; only the
72
+ build / CI tooling and CHANGELOG differ.
73
+
74
+ - **Bundle-size budgets (F6).** `size-limit` is wired into CI with
75
+ per-entry-point budgets defined in `package.json`:
76
+
77
+ | Entry point | Budget |
78
+ | --------------------------------------- | ------ |
79
+ | `@dojocoding/whatsapp` (root, ESM/CJS) | 100 KB |
80
+ | `@dojocoding/whatsapp/express` | 6 KB |
81
+ | `@dojocoding/whatsapp/web` | 3 KB |
82
+ | `@dojocoding/whatsapp/hono` | 3 KB |
83
+ | `@dojocoding/whatsapp/storage/redis` | 2 KB |
84
+ | `@dojocoding/whatsapp/storage/postgres` | 4 KB |
85
+
86
+ Limits are roughly 1.5× the current measured sizes, so a single PR
87
+ cannot double a bundle by accident (e.g. an accidental
88
+ `import "lodash"`). Run `pnpm size` locally to see the same
89
+ budget report CI runs.
90
+
91
+ ## [0.7.3] — 2026-05-10
92
+
93
+ ### Tests (no SDK behaviour change)
94
+
95
+ Phase 3 of the Track F + test-coverage audit hardening pass.
96
+ 572 tests pass (was 524 — +48 across the seven audit items below).
97
+ The published artefact is functionally identical to 0.7.2; only the
98
+ test suite and CHANGELOG differ.
99
+
100
+ - **Property-based assertions** with `fast-check` for three
101
+ high-leverage modules:
102
+ - `webhooks/signature.ts` — HMAC verifier invariants across
103
+ random body / secret / header inputs.
104
+ - `webhooks/dedupe.ts` — dedupe-key identity + Unicode handling.
105
+ - `client/retry.ts` — `fullJitterDelay` math bounded by
106
+ `[floorMs, min(maxDelayMs, expCap)]` for any RNG output.
107
+ - **Concurrent dedupe race test** — 100 parallel `handlePayload`
108
+ calls with the same wamid invoke the handler exactly once;
109
+ validates the single-flight semantics under Meta's aggressive
110
+ retry pattern.
111
+ - **Storage failure propagation** — explicit assertions that
112
+ `WebhookDeduper`, `WindowTracker`, and `WebhookReceiver`
113
+ surface storage errors rather than swallowing them silently.
114
+ - **`sendReply` template-path coverage** — the window-exempt path
115
+ for template + reaction payloads through `sendReply`, plus the
116
+ window-gated path for free-form payloads, all asserted against
117
+ captured wire bodies.
118
+ - **Local pack-contents smoke** — `test/contract/pack-contents.test.ts`
119
+ mirrors the CI "Verify pack contents (dry-run)" assertion so
120
+ `pnpm test` catches a `files` allowlist regression before CI.
121
+
122
+ ## [0.7.2] — 2026-05-11
123
+
124
+ ### Changed (CI / repo hygiene only — no SDK behaviour change)
125
+
126
+ - Dependabot now opens grouped weekly PRs for npm + GitHub Actions
127
+ updates. `@types/*`, lint/format tooling, vitest/msw, and the
128
+ `@opentelemetry/*` family ship as batched sweeps. Major-version
129
+ bumps to peer-dep ecosystems (express, hono, ioredis, pg) are
130
+ ignored — those need an explicit decision about widening the
131
+ supported range.
132
+ - CodeQL static-analysis workflow added (`.github/workflows/codeql.yml`)
133
+ with the `security-extended` query suite. Runs on push, PR, and
134
+ a weekly Monday schedule. Findings appear in the Security tab.
135
+ - `pnpm audit --prod --audit-level=moderate` runs on every CI build
136
+ (continue-on-error initially, so advisories surface in the run
137
+ log without blocking PRs).
138
+ - PR template + three issue templates (bug, feature, compliance
139
+ drift) shipped. `.github/CODEOWNERS` pins compliance-relevant
140
+ files and the release pipeline.
141
+
142
+ No npm-published artefact changes from 0.7.1.
143
+
144
+ ## [0.7.1] — 2026-05-11
145
+
146
+ ### Added
147
+
148
+ - **`verifySignatureOrThrow(input)`** — throwing variant of
149
+ `verifySignature` exported from the root entry. Resolves silently
150
+ on a valid signature; throws `WebhookSignatureError` on bad HMAC,
151
+ missing header, malformed hex, or wrong byte length. Use this when
152
+ wiring your own HTTP layer (not the SDK's Express / web / Hono
153
+ adapters) and you want a typed error rather than a boolean.
154
+
155
+ ### Changed
156
+
157
+ - **CI: bumped GitHub Actions to Node 24-compatible versions** —
158
+ `actions/checkout@v5`, `actions/setup-node@v6`,
159
+ `pnpm/action-setup@v6`, `actions/upload-artifact@v7`,
160
+ `softprops/action-gh-release@v3`. Removes the deprecation banner
161
+ on every CI run; ready for Meta's 2026-06-02 Node 20 default
162
+ removal.
163
+
164
+ ### Tests
165
+
166
+ - Added `test/contract/public-surface.test.ts` — a drift detector
167
+ asserting every documented value/class/factory across the root
168
+ entry and all five subpaths (`/express`, `/web`, `/hono`,
169
+ `/storage/redis`, `/storage/postgres`) is reachable at runtime.
170
+ If a sub-module export is added without being plumbed through, or
171
+ a documented export is accidentally renamed/removed, this test
172
+ fails before consumers do.
173
+ - Added negative-path coverage for `WebhookSignatureError` (5 new
174
+ tests via `verifySignatureOrThrow`) and `MockModeError` (4 new
175
+ contract tests pinning the public shape).
176
+ - Re-shimmed `test/integration/express/middleware.test.ts` to use a
177
+ Promise-resolved-by-handler pattern instead of 5 ms `setTimeout`
178
+ waits and wall-clock ack-timing windows — mirrors the
179
+ determinism fix already applied to the web adapter test in 0.2.0.
180
+
181
+ 524 tests pass (was 447).
182
+
183
+ ## [0.7.0] — 2026-05-11
184
+
185
+ ### Added
186
+
187
+ - **Authentication template (OTP) builder.** `buildAuthTemplate({
188
+ to, name, language, otp, otpButtonIndex? })` and the matching
189
+ `client.sendAuthTemplate(...)` produce the documented copy-code /
190
+ one-tap / zero-tap wire payload with the OTP code duplicated into
191
+ both the body and URL-button parameters (the canonical footgun
192
+ this builder exists to remove). OTP length validated against
193
+ Meta's 15-char ceiling.
194
+ - **Voice-note builder.** `buildVoice({ to, id|link })` and
195
+ `client.sendVoice(...)` produce audio messages with `voice: true`,
196
+ triggering transcription support, auto-download, and the "played"
197
+ delivery status.
198
+ - **Carousel-template builder.** `buildCarouselTemplate({ to, name,
199
+ language, bodyParameters?, cards })` and
200
+ `client.sendCarouselTemplate(...)` produce media-card carousel
201
+ template sends with 1–10 cards. Each card's `card_index` is
202
+ computed from iteration order; consumers can't misorder it.
203
+ - **Limited-time-offer template support.** Three new
204
+ `TemplateParameter` union variants:
205
+ `TemplateParameterLimitedTimeOffer`,
206
+ `TemplateParameterCouponCode`, and `TemplateParameterPayload`.
207
+ `TemplateComponent.type` widens to accept `"carousel"` and
208
+ `"limited_time_offer"`. Use via the existing `buildTemplate(...)`
209
+ /
210
+ `client.sendTemplate(...)`.
211
+ - New `CarouselCardComponent` exported type. `AudioMessage.audio`
212
+ gains an optional `voice?: boolean` field.
213
+
214
+ Every wire shape is grounded in a Meta doc URL referenced in
215
+ source-file JSDoc and pinned via byte-for-byte snapshot tests:
216
+
217
+ - https://developers.facebook.com/documentation/business-messaging/whatsapp/templates/authentication-templates/copy-code-button-authentication-templates/
218
+ - https://developers.facebook.com/documentation/business-messaging/whatsapp/messages/audio-messages
219
+ - https://developers.facebook.com/documentation/business-messaging/whatsapp/templates/marketing-templates/media-card-carousel-templates/
220
+ - https://developers.facebook.com/documentation/business-messaging/whatsapp/templates/marketing-templates/limited-time-offer-templates/
221
+
222
+ ## [0.6.0] — 2026-05-11
223
+
224
+ ### Added
225
+
226
+ - **Redis and Postgres `Storage` adapters at subpath exports.**
227
+ - `@dojocoding/whatsapp/storage/redis` exports
228
+ `createRedisStorage(client, options?)`. Takes an
229
+ `ioredis`-compatible client; uses native `SET PX` / `SET NX`
230
+ for TTL and atomicity. `ioredis` is an optional peer
231
+ dependency on `^5.0.0`.
232
+ - `@dojocoding/whatsapp/storage/postgres` exports
233
+ `createPostgresStorage(client, options?)` and
234
+ `POSTGRES_STORAGE_SCHEMA: string`. Takes a `pg`-compatible
235
+ client; runs four SQL statements (`SELECT`, two `INSERT ... ON
236
+ CONFLICT`, `DELETE`). `pg` is an optional peer dependency on
237
+ `^8.0.0`.
238
+ - Both adapters use minimal structural interfaces (`RedisLike`,
239
+ `PgLike`) so the SDK doesn't import either library at runtime;
240
+ consumers pass any compatible client (including test fakes).
241
+ - See [`docs/storage.md`](./docs/storage.md).
242
+ - Shared `Storage` contract test:
243
+ `test/unit/storage/contract.ts` exports a parametrised suite
244
+ that every implementation (`InMemoryStorage`,
245
+ `createRedisStorage`, `createPostgresStorage`) runs against —
246
+ drift between implementations is impossible-to-not-notice.
247
+
248
+ ## [0.5.0] — 2026-05-11
249
+
250
+ ### Added
251
+
252
+ - **`withRateLimit(client, options?)` decorator + `TokenBucket` /
253
+ `BucketMap` primitives.** Wraps any `WhatsAppLikeClient` and
254
+ throttles `send*` calls at a per-pair bucket (default 1 msg per
255
+ 6 s) and a per-WABA bucket (default 80 MPS) before delegating to
256
+ the wrapped client. Caller surface unchanged — the queue is
257
+ invisible. Lower-level `TokenBucket` and `BucketMap` are exported
258
+ for non-WhatsApp use cases. See [`docs/queue.md`](./docs/queue.md).
259
+ - New OTel span `whatsapp.queue.acquire` exposes queue latency
260
+ separately from network latency, with PII-redacted recipient /
261
+ WABA attributes.
262
+
263
+ ## [0.4.0] — 2026-05-10
264
+
265
+ ### Added
266
+
267
+ - **`TokenProvider` callback for `WhatsAppClient`.**
268
+ `WhatsAppClientOptions.token` now accepts
269
+ `string | (() => string | Promise<string>)`. The SDK resolves the
270
+ callback once per outer request — all retries within a single
271
+ request reuse the same resolved value. Closes the race window in
272
+ the previous "swap the client instance per tenant on
273
+ `AuthenticationError`" rotation pattern. The `TokenProvider` type
274
+ is exported from the root entry. See
275
+ [`docs/client.md`](./docs/client.md) and
276
+ [`docs/patterns.md`](./docs/patterns.md) § 5.
277
+ - Provider errors (throw, empty string, non-string return) surface
278
+ as `AuthenticationError` before the HTTP request is made, with
279
+ the underlying error attached as `cause`.
280
+
281
+ ### Changed
282
+
283
+ - **BREAKING (pre-1.0 minor):** the `@internal`
284
+ `WhatsAppClient._getBearerToken(): string` is removed and replaced
285
+ with `WhatsAppClient._resolveBearerToken(): Promise<string>`.
286
+ External callers MUST NOT depend on internal accessors; the legacy
287
+ helper is gone.
288
+
289
+ ## [0.3.0] — 2026-05-10
290
+
291
+ ### Added
292
+
293
+ - **`@dojocoding/whatsapp/hono` subpath** — typed Hono `Handler`
294
+ wrapper around the web-standard core. Mount with
295
+ `app.all(path, whatsappHandler(receiver))`. See
296
+ [`docs/hono.md`](./docs/hono.md) and
297
+ [`docs/cookbook/hono.md`](./docs/cookbook/hono.md). Hono is an
298
+ optional peer dependency on `^4.0.0`.
299
+
300
+ ## [0.2.0] — 2026-05-10
301
+
302
+ ### Added
303
+
304
+ - **`@dojocoding/whatsapp/web` subpath** — Fetch-API
305
+ (`Request → Response`) handler usable on Cloudflare Workers, Bun,
306
+ Deno, Hono, Next.js App Router, and any WinterCG runtime. See
307
+ [`docs/web.md`](./docs/web.md) and
308
+ [`docs/cookbook/cloudflare-workers.md`](./docs/cookbook/cloudflare-workers.md).
309
+ - WebCrypto migration of `verifySignature`, `verifyHandshake`, and
310
+ `hashPhoneNumberId` — these now run unmodified on any WinterCG
311
+ runtime in addition to Node. Byte-identical output to the previous
312
+ `node:crypto` implementations, verified by parity tests.
313
+
314
+ ### Changed
315
+
316
+ - **BREAKING (pre-1.0 minor):** `verifySignature`, `computeSignature`,
317
+ and `hashPhoneNumberId` are now `async`. Internal call sites are
318
+ updated; external callers must `await` the return value.
319
+ - **BREAKING (pre-1.0 minor):** `WebhookReceiver.verify` and
320
+ `WebhookReceiver.handlePayload` are now `async`. The return shape
321
+ (`{ status, dispatchPromise }`) is unchanged; the receiver now
322
+ resolves to it via a Promise.
323
+ - The Express adapter is now a thin shim over the web-standard core
324
+ (`createWhatsAppHandler`). Externally observable behaviour is
325
+ unchanged; the integration suite passes without modification.
326
+
327
+ ## [0.1.0] — 2026-05-10
328
+
329
+ First public release. Eight capability slices, all proposed and merged through
330
+ OpenSpec; see `openspec/changes/archive/` for the per-capability proposal,
331
+ design, spec deltas, and tasks.
332
+
333
+ ### Added
334
+
335
+ - **Cloud API client** (`WhatsAppClient`) — HTTP transport with bearer-token
336
+ auth, exponential-backoff retry with full jitter on 408/429/5xx and Meta
337
+ recoverable codes (130429 / 131048 / 131053 / 131056), `Retry-After`
338
+ honouring, token-debug health check, per-instance Graph API version pin.
339
+ - **Message builders** — typed builders and `client.send*` convenience methods
340
+ for text, image, video, audio, document, sticker, location, contacts,
341
+ interactive (button / list / cta_url), template, reaction, and reply
342
+ messages.
343
+ - **Webhook receiver** (`WebhookReceiver`) — GET-handshake verify-token check,
344
+ raw-body HMAC-SHA256 signature verification with `crypto.timingSafeEqual`,
345
+ polymorphic event parsing (`message`, `status`, `template_status`,
346
+ `template_quality_update`, `template_category_update`,
347
+ `phone_number_quality_update`, `account_alert`, `account_review`,
348
+ `unknown`), `wamid` dedupe through a pluggable `Storage`, and 30-second-ack
349
+ async dispatch.
350
+ - **24-hour window tracker** (`WindowTracker`) — pluggable `Storage`-backed
351
+ customer-service window state; `client.send*` throws `WindowClosedError`
352
+ pre-flight when the window is closed. Templates and reactions are
353
+ window-exempt.
354
+ - **Template management** — list / get approved templates; 1-indexed
355
+ contiguous `{{N}}` placeholder validation; cross-validation of template
356
+ components against placeholder counts before send.
357
+ - **Mock mode** (`MockWhatsAppClient`, `pickWhatsAppClient`) — shared
358
+ `WhatsAppLikeClient` interface with the real client, parity-tested across a
359
+ cross-client matrix. Records sends in memory; optional in-memory template
360
+ registry. No Meta credentials required.
361
+ - **Observability** — OpenTelemetry spans on every Graph API request and
362
+ every webhook-handler invocation, with PII-redacting `hashPhoneNumberId`
363
+ (configurable salt via `setRedactSalt`). `@opentelemetry/api` is an
364
+ optional peer dependency.
365
+ - **Express adapter** (`@dojocoding/whatsapp/express`) — middleware that
366
+ captures raw bytes before any JSON parser, handles `GET`/`POST` method
367
+ routing, and acks within 30 seconds.
368
+ - **Typed error classes** — `WhatsAppError`, `RateLimitError`,
369
+ `WindowClosedError`, `WebhookSignatureError`, `TemplateError`,
370
+ `MissingCredentialsError`, `MockModeError`, `AuthenticationError`,
371
+ `PermissionError`, `CapabilityError`. Branch with `instanceof`, not
372
+ string matching.
373
+ - **Storage interface** — `Storage` with TTL semantics and `InMemoryStorage`
374
+ reference implementation. Shared by `WindowTracker` and the webhook
375
+ deduper.
376
+
377
+ ### Compliance
378
+
379
+ - Graph API pin: `v25.0`.
380
+ - Webhook dedupe TTL: 24 hours (covers Meta's up-to-7-day delivery retries
381
+ practically; full 7-day TTL is opt-in via `Storage` config).
382
+ - Webhook ack deadline: 30 seconds.
383
+
384
+ ### Project
385
+
386
+ - Spec-driven via [OpenSpec](https://github.com/openspec-dev/openspec); eight
387
+ stable specs under `openspec/specs/`.
388
+ - Dual ESM + CJS build via `tsup`; `Node >= 20` LTS.
389
+ - Test layers: unit, contract, integration, parity. Coverage thresholds
390
+ enforced in CI (line ≥ 90 %, branch ≥ 85 %).
391
+ - Licensed under [MIT](./LICENSE).
392
+
393
+ [0.7.3]: https://github.com/DojoCodingLabs/whatsapp-adapter/releases/tag/v0.7.3
394
+ [0.7.2]: https://github.com/DojoCodingLabs/whatsapp-adapter/releases/tag/v0.7.2
395
+ [0.7.1]: https://github.com/DojoCodingLabs/whatsapp-adapter/releases/tag/v0.7.1
396
+ [0.7.0]: https://github.com/DojoCodingLabs/whatsapp-adapter/releases/tag/v0.7.0
397
+ [0.6.0]: https://github.com/DojoCodingLabs/whatsapp-adapter/releases/tag/v0.6.0
398
+ [0.5.0]: https://github.com/DojoCodingLabs/whatsapp-adapter/releases/tag/v0.5.0
399
+ [0.4.0]: https://github.com/DojoCodingLabs/whatsapp-adapter/releases/tag/v0.4.0
400
+ [0.3.0]: https://github.com/DojoCodingLabs/whatsapp-adapter/releases/tag/v0.3.0
401
+ [0.2.0]: https://github.com/DojoCodingLabs/whatsapp-adapter/releases/tag/v0.2.0
402
+ [0.1.0]: https://github.com/DojoCodingLabs/whatsapp-adapter/releases/tag/v0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dojo Coding LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.