Initial Commit
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
# `webhook-channel` — HTTP webhook delivery channel
|
||||
|
||||
## Purpose
|
||||
|
||||
`webhook-channel.ts` ships the `WebhookChannel` implementation that POSTs notifications to an arbitrary HTTPS endpoint. The notification's `recipient` field IS the destination URL, the body carries the serialised notification payload, and the channel respects a per-request timeout via `AbortController`. The fetch surface is injectable via `FetchLike` from `@suaveplan/types`, so tests run deterministically against `FakeFetch` and downstream consumers can plug their own observability-instrumented fetch.
|
||||
|
||||
## Features
|
||||
|
||||
- Injectable `FetchLike` — no `globalThis.fetch` patching.
|
||||
- Configurable timeout (default 10_000 ms) using `AbortController`.
|
||||
- Custom headers via `WebhookChannelConfig.headers`.
|
||||
- `canSend` enforces `http://` or `https://` recipient prefix.
|
||||
- Returns `DeliveryResult { success: false }` on non-2xx, network error, abort, or unexpected throw.
|
||||
- Injectable `Clock` for deterministic `timestamp` in tests.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { WebhookChannel } from "@suaveplan/notification-channel";
|
||||
|
||||
const channel = new WebhookChannel({
|
||||
headers: { Authorization: "Bearer secret" },
|
||||
});
|
||||
const r = await channel.send(notification);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import { FakeFetch, WebhookChannel } from "@suaveplan/notification-channel";
|
||||
|
||||
// In tests:
|
||||
const fake = new FakeFetch();
|
||||
fake.push({ status: 200 });
|
||||
const channel = new WebhookChannel({
|
||||
fetchFn: fake.fetch,
|
||||
timeoutMs: 500,
|
||||
clock: () => testNow,
|
||||
});
|
||||
|
||||
// In prod, wire a real instrumented fetch:
|
||||
const channel2 = new WebhookChannel({
|
||||
fetchFn: (url, init) => instrumented.fetch(url, init),
|
||||
headers: { "X-Signed-By": "suaveplan" },
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `WebhookChannel(config?: WebhookChannelConfig)` — constructor.
|
||||
- `WebhookChannelConfig` — `{ headers?, fetchFn?, timeoutMs?, clock? }`.
|
||||
- `canSend(n): boolean`, `send(n): Promise<DeliveryResult>`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The POST body is built from `{ id, subject, body, priority, metadata, createdAt }` — a stable shape so webhook consumers can lock their parsers. `Content-Type: application/json` is always set; consumer headers override the default. Aborted fetches surface `error: "aborted"` (matching the `AbortError.name`-derived message from the polyfill). Non-2xx responses encode the upstream status into `error: "HTTP <status>: <statusText>"`. Both `Error` and non-`Error` throws inside the fetch callback are stringified into `error` so the `DeliveryResult` contract holds.
|
||||
Reference in New Issue
Block a user