3.3 KiB
webhook/slack-webhook — Slack incoming-webhook client
(no submodules)
Purpose
SlackWebhook is the simpler half of the package — a typed wrapper around Slack's incoming-webhook URLs. Where SlackApiClient requires an OAuth token and supports the full Web API, the webhook client takes only the URL and POSTs { text, blocks? } payloads. It exists because many ops surfaces (CI bots, alerting, build notifications) only need fire-and-forget delivery and the webhook URL is the lower-friction credential — no token rotation, no scoped permissions. The client returns a SlackDeliveryReport on success and surfaces typed errors on every failure path so retry middleware can pattern-match on .code.
Features
- Zod boundary validation in the constructor — invalid
webhookUrlrejects withSlackConfigMissingError. - Three failure paths — network/timeout (
SlackTimeoutError), HTTP 401/403 (SlackAuthError), other 4xx/5xx (SlackWebhookFailedError). - Injectable
Clockfor deterministic message-id timestamps. - Optional
Rnginjection — when supplied, the message-id suffix usesrng.nextUint32().toString(36)for uniqueness across rapidly-issued sends; otherwise falls back to the monotonic counter. slack.webhook_sendsandslack.errorsOTel instruments emitted on every path.- Caller-supplied
AbortSignalAND internaltimeoutMs(default 10 000 ms) both honoured.
Usage: Basic
import { SlackWebhook } from "@suaveplan/slack-client";
const webhook = new SlackWebhook({
webhookUrl: "https://hooks.slack.com/services/T0/B0/X",
});
const report = await webhook.sendText("Deploy complete.");
Usage: Advanced
import { SlackWebhook } from "@suaveplan/slack-client";
import { FakeFetch } from "@suaveplan/slack-client/fakes/fake-fetch";
const fake = new FakeFetch();
fake.push({ status: 200 });
let counter = 0;
const webhook = new SlackWebhook({
webhookUrl: "https://hooks.slack.com/services/T/B/X",
fetch: fake.fetch,
clock: () => 1_700_000_000_000 + counter++,
rng: { nextUint32: () => 42, nextFloat: () => 0.42 },
logger: { debug() {}, info() {}, warn() {}, error() {} },
});
const report = await webhook.send({
text: "Block kit",
blocks: [{ type: "section", text: { type: "mrkdwn", text: "*Hi*" } }],
});
API Reference
new SlackWebhook(options: SlackWebhookOptions)send(message: { text: string; blocks?: SlackBlock[] }, signal?: AbortSignal): Promise<SlackDeliveryReport>sendText(text: string, signal?: AbortSignal): Promise<SlackDeliveryReport>
Errors thrown: SlackConfigMissingError, SlackTimeoutError, SlackAuthError, SlackWebhookFailedError.
Implementation Notes
The message-id format is webhook-<ms>-<counter>-<rngOrCounter> so even rapid bursts on the same clock tick produce distinct ids. The counter is monotone within a single instance; injecting an Rng adds cross-instance uniqueness without coupling to crypto.randomUUID (which is universally available but adds an unwanted dependency on the runtime's CSPRNG availability in test environments). The constructor explicitly binds globalThis.fetch to globalThis when used as default — without the bind, fetch.call(undefined, ...) triggers an "Illegal invocation" TypeError in some browser builds.