Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
# MessageBird — `@suaveplan/sms-providers`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Real MessageBird SMS provider implementing `SmsProvider` from `@suaveplan/sms`. POSTs to `/messages` with `Authorization: AccessKey <key>`; status fetched via `GET /messages/{id}`. Inbound webhook signature verification follows the MessageBird Signed Webhooks v2 (`Messagebird-Signature-Jwt`) spec — a JWT HS256 carrying `iat`/`exp`/`nbf`/`payload_hash` claims; the verifier checks the timestamp window (default ±5 minutes), re-MACs the canonical `header.payload`, and constant-time compares the base64url-decoded signature.
The class holds no in-flight state and routes every HTTP call through the injectable `FetchLike` seam. Tests stand up a `node:http` server on a random port and inject a `fetch` adapter that points at it.
## Features
- `AccessKey` auth header per MessageBird's documented scheme.
- JSON body with `recipients[]`, `originator`, `body`.
- Status mapping covers `scheduled` / `sent` / `buffered` / `delivered` / `expired` / `delivery_failed`.
- JWT HS256 webhook verifier with `iat` / `exp` / `nbf` window enforcement and optional `payload_hash` cross-check.
- Injectable `nowMs` + `toleranceMs` so tests pin the clock and the production deployment can widen the freshness window if needed.
- `parseInbound` discriminates between inbound messages and DLRs, recognising the standard CTIA opt-out keywords.
- OTel — `send_total` + `send_duration_ms` + `signature_failures_total` + `webhook_verify_duration_ms`.
## Usage: Basic
```ts
import { MessageBirdProvider } from "@suaveplan/sms-providers";
const mb = new MessageBirdProvider({ accessKey: "test_…" });
await mb.send({ to: "+15551234567", from: "Suaveplan", body: "hi" });
```
## Usage: Advanced
```ts
import { MessageBirdProvider } from "@suaveplan/sms-providers";
const mb = new MessageBirdProvider({ accessKey: "live_…" });
const ok = await mb.verifyWebhookSignature(
"<header>.<payload>.<sig>",
"shared-signing-key",
{ bodyHashHex: "<sha256-hex>", nowMs: Date.now(), toleranceMs: 60_000 }
);
```
## API Reference
| Symbol | Kind | Notes |
|---|---|---|
| `MessageBirdProvider` | class | Implements `SmsProvider`. |
| `send(message, options?)` | method | `POST /messages`, returns `SmsDeliveryReport`. |
| `getStatus(id)` | method | `GET /messages/{id}`. |
| `mapStatus(raw)` | method | Canonical mapping. |
| `verifyWebhookSignature(token, signingKey, options?)` | method | JWT HS256 verifier; throws `SIGNATURE_INVALID` on failure. |
| `parseInbound(body)` | method | Discriminates into `InboundSmsEvent`. |
## Implementation Notes
The verifier handles both standard Base-64 and base64url (`-`/`_` → `+`/`/`) inputs because MessageBird's JWT uses the URL-safe variant. `payload_hash` is checked only when both the JWT carries it and the caller supplies a `bodyHashHex`; missing on either side is treated as "skip" so callers can disable the body binding when their proxy strips bodies.