first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
# AWS SNS — `@suaveplan/sms-providers`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Real AWS SNS SMS provider implementing `SmsProvider` from `@suaveplan/sms`. POSTs `Action=Publish` form bodies to the regional SNS endpoint (`https://sns.<region>.amazonaws.com/`) signed with inline AWS Signature V4 (see `./sigv4`); SMS-specific message attributes (`AWS.SNS.SMS.SMSType` and `AWS.SNS.SMS.SenderID`) are forwarded when supplied on the provider config. Inbound SNS HTTP/S notifications — subscription confirmation, delivery callbacks, unsubscribe confirmation — are validated through `verifyNotification` which delegates the RSA-SHA1 verification step to an injectable callback so this package never imports `node:crypto`.
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
- Inline AWS Signature V4 signing (no `@aws-sdk` dep tree).
- `AWS.SNS.SMS.SMSType` defaults to `Transactional`; override via `config.smsType: "Promotional"`.
- `AWS.SNS.SMS.SenderID` forwarded from the message `from` or the config `senderId`.
- Optional `sessionToken` for STS-issued credentials (`X-Amz-Security-Token`).
- Status mapping covers `pending` / `success` / `failure` / `optout`.
- `verifyNotification(payload, rsaVerify)` validates the documented SNS signing chain — fetches the signing cert URL through the injectable fetch, builds the canonical string-to-sign (different field order for `Notification` vs `SubscriptionConfirmation`/`UnsubscribeConfirmation`), and delegates the RSA verification to the supplied callback.
- `parseInbound` discriminates between delivery-receipt notifications (Subject = `"AWS SMS Delivery Receipt"`) and inbound mobile-originated SMS payloads; recognises CTIA opt-out keywords on the inbound body.
- OTel — counter + histogram on every send + verify.
## Usage: Basic
```ts
import { AwsSnsProvider } from "@suaveplan/sms-providers";
const sns = new AwsSnsProvider({
region: "us-east-1",
accessKeyId: "AKIA…",
secretAccessKey: "…",
senderId: "SUAVEPLAN",
});
const r = await sns.send({ to: "+15551234567", body: "Hello" });
```
## Usage: Advanced
```ts
import { AwsSnsProvider } from "@suaveplan/sms-providers";
import { createVerify } from "node:crypto";
const sns = new AwsSnsProvider({
region: "us-east-1",
accessKeyId: "AKIA…",
secretAccessKey: "…",
sessionToken: "FwoGZXIvYXdz…",
smsType: "Transactional",
});
// Node-side RSA-SHA1 verifier callback.
const rsaVerify = async (pem: string, msg: string, sigB64: string) =>
createVerify("RSA-SHA1").update(msg).end().verify(pem, sigB64, "base64");
const ok = await sns.verifyNotification(
/* payload */ {
Type: "Notification",
MessageId: "abc",
TopicArn: "arn:aws:sns:…",
Subject: "AWS SMS Delivery Receipt",
Message: JSON.stringify({ status: "SUCCESS" }),
Timestamp: new Date().toISOString(),
Signature: "<base64>",
SignatureVersion: "1",
SigningCertURL:
"https://sns.us-east-1.amazonaws.com/SimpleNotificationService-XXX.pem",
},
rsaVerify
);
```
## API Reference
| Symbol | Kind | Notes |
|---|---|---|
| `AwsSnsProvider` | class | Implements `SmsProvider`. |
| `send(message, options?)` | method | SigV4-signed POST `Action=Publish`. |
| `getStatus(id)` | method | Returns a synthetic `queued` report (SNS does not expose per-message status). |
| `mapStatus(raw)` | method | Canonical mapping. |
| `verifyNotification(payload, rsaVerify)` | method | Delegates RSA-SHA1 verification to the supplied callback. |
| `parseInbound(payload)` | method | Discriminates into `InboundSmsEvent`. |
| `buildSnsStringToSign(payload)` | helper | Exported for advanced use. |
## Implementation Notes
`getStatus` deliberately returns a synthetic `queued` report rather than synthesise a fake `delivered` — SNS reports SMS lifecycle exclusively via the configured delivery-status SNS topic (typically wired into CloudWatch Logs in production), and lying about provider behaviour would mislead consumers. The recommended production pipeline is to subscribe to the delivery-status topic, route the notifications back through the provider's `parseInbound`, and feed the resulting `delivery_receipt` events to `@suaveplan/sms`'s `DeliveryTracker`. The `senderId` config overrides the per-message `from`; this matches the AWS console UX where the account-level Sender ID is the production default.