import * as WebhookDestination from '../WebhookDestination.js' /** * Builds a `betterstack` destination. Its `deliver` formats the envelope into a * structured log event and POSTs it to the Better Stack ingest URL authenticated * with the source token (`Authorization: Bearer …`; host-locked to * `*.betterstackdata.com`). Deliver with `.deliver({ envelope })`. */ export function betterStack( options: betterStack.Options, ): WebhookDestination.Instance { const { token, url } = options assertBetterstackUrl(url) return { deliver(deliverOptions) { const { envelope } = deliverOptions return WebhookDestination.send({ // A structured log event: a human-readable `message`, a `dt` timestamp // (Better Stack indexes this as the event time), and the raw event // fields flattened alongside for querying. body: JSON.stringify({ chainId: envelope.chainId, data: envelope.data, ...(envelope.context?.description === undefined ? {} : { description: envelope.context.description }), dt: envelope.createdAt, event: envelope.type, eventId: envelope.id, // The subscription's `context.title` (what the webhook is for) becomes // the log `message` when set; otherwise fall back to the event identity. message: envelope.context?.title ?? (envelope.type === 'ping' ? 'Tempo webhook test ping' : `Tempo event: ${envelope.type}`), ...(envelope.context?.metadata === undefined ? {} : { metadata: envelope.context.metadata }), subscriptionId: envelope.subscriptionId, }), errorPrefix: 'betterstack ', fetch: deliverOptions.fetch, headers: { authorization: `Bearer ${token}` }, now: deliverOptions.now, timeoutMs: deliverOptions.timeoutMs, url, }) }, token, type: 'betterstack', url, } } export declare namespace betterStack { /** Options for {@link betterStack}. */ type Options = { /** Better Stack source token, sent as `Authorization: Bearer …`. */ token: string /** Better Stack ingest host URL (`https://.betterstackdata.com`). */ url: string } } /** Host suffix that Better Stack ingest URLs must use. */ const betterstackHostSuffix = '.betterstackdata.com' /** * Validates a `betterstack` destination's ingest URL: must be `https` and hosted * under {@link betterstackHostSuffix} (`s95.eu-nbg-2.betterstackdata.com` etc.). * As with Slack, the host lock keeps `betterstack` from becoming a generic * "POST anywhere with a bearer token" primitive — the source token is attached * server-side, so the request must only ever reach Better Stack. */ export function assertBetterstackUrl(input: string): URL { let url: URL try { url = new URL(input) } catch { throw new WebhookDestination.InvalidUrlError('malformed', input) } if (url.protocol !== 'https:') throw new WebhookDestination.InvalidUrlError('protocol', input) if (url.username || url.password) throw new WebhookDestination.InvalidUrlError('credentials', input) if (!url.hostname.endsWith(betterstackHostSuffix)) throw new WebhookDestination.InvalidUrlError('betterstack_host', input) return url }