Move docs/ package mirror into document-navigator plugin
Relocate the ~6k-file docs tree from the repo root into plugins/document-navigator/docs/ so the plugin is self-contained and portable. docs/superpowers/ (specs and plans) stays at the repo root. Moved via cp+rm rather than git mv because directory rename() fails with EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the identical-content files at commit time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# Vonage — `@suaveplan/sms-providers`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Real Vonage (formerly Nexmo) SMS provider implementing `SmsProvider` from `@suaveplan/sms`. POSTs to `/sms/json` with `api_key` + `api_secret` carried in the form body (the documented default authentication mode), auto-selecting `type=unicode` when the body contains a non-ASCII codepoint and `type=text` otherwise. Sender (`from`) supports alphanumeric IDs (up to 11 chars), short codes, and long codes — Vonage accepts the raw string and we pass it through verbatim. Status is fetched via `GET /search/message`. Webhook signature verification follows Vonage's documented HMAC-SHA256 scheme over the sorted form fields excluding the `sig` parameter.
|
||||
|
||||
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
|
||||
|
||||
- Form-body auth (`api_key`, `api_secret`) — matches Vonage's documented default.
|
||||
- Auto `type=unicode` vs `type=text` based on whether the body contains a non-ASCII codepoint.
|
||||
- Sender field forwarded verbatim (alpha / short code / long code all supported).
|
||||
- `getStatus(id)` round-trips `GET /search/message?id=…` with the same auth pattern.
|
||||
- `mapStatus` covers `submitted` / `buffered` / `accepted` / `delivered` / `expired` / `failed` / `rejected`.
|
||||
- `verifyWebhookSignature(params, signature, signatureSecret)` — HMAC-SHA256 over `&key=value&…` for sorted keys (excluding `sig`); lowercase-hex comparison in constant time.
|
||||
- `parseInbound(params)` discriminates between inbound messages and DLR (delivery-receipt) payloads.
|
||||
- OTel — counter + histogram for every send + verify.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { VonageProvider } from "@suaveplan/sms-providers";
|
||||
|
||||
const vonage = new VonageProvider({
|
||||
apiKey: "vonage_api_key",
|
||||
apiSecret: "vonage_api_secret",
|
||||
});
|
||||
const r = await vonage.send({
|
||||
to: "447700900000",
|
||||
from: "Suaveplan",
|
||||
body: "Hello",
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { VonageProvider } from "@suaveplan/sms-providers";
|
||||
|
||||
const vonage = new VonageProvider({
|
||||
apiKey: "k",
|
||||
apiSecret: "s",
|
||||
timeoutMs: 5_000,
|
||||
});
|
||||
|
||||
const ok = await vonage.verifyWebhookSignature(
|
||||
{ messageId: "0A0000001", text: "hi", sig: "<hex>" },
|
||||
"<hex>",
|
||||
"signatureSecretSharedWithVonage"
|
||||
);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Symbol | Kind | Notes |
|
||||
|---|---|---|
|
||||
| `VonageProvider` | class | Implements `SmsProvider`. |
|
||||
| `send(message, options?)` | method | POST `/sms/json`, returns `SmsDeliveryReport`. |
|
||||
| `getStatus(id)` | method | `GET /search/message?id=…`. |
|
||||
| `mapStatus(raw)` | method | Canonical mapping. |
|
||||
| `verifyWebhookSignature(params, sig, signatureSecret)` | method | HMAC-SHA256 hex verifier; throws `SIGNATURE_INVALID` on mismatch. |
|
||||
| `parseInbound(params)` | method | Discriminates into `InboundSmsEvent`. |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Vonage strips the leading `+` from E.164 `to` numbers on submission, so the implementation `replace(/^\+/, "")` once before form-encoding. The signature payload is the documented `&{k}={v}&…` shape (note the leading `&`), built from a stable lexicographic key sort with `sig` excluded.
|
||||
Reference in New Issue
Block a user