import type { Channel, DeliveryResult, Notification } from './types'; import { discordChannel } from './discord'; import { apnsChannel } from './apns'; // Fan a notification out to every configured channel. // // Channels are independent and none of them may break a producer: a job that finished has finished // whether or not a banner appeared. So every channel is awaited with its own error boundary, and the // dispatcher always resolves. const channels: Channel[] = [apnsChannel, discordChannel]; /** Registered here rather than imported at the top so channels can be added without touching producers. */ export function registerChannel(channel: Channel): void { channels.push(channel); } export function configuredChannels(): string[] { return channels.filter((c) => c.isConfigured()).map((c) => c.name); } export async function dispatch(n: Notification): Promise { const active = channels.filter((c) => c.isConfigured()); if (active.length === 0) return []; return Promise.all( active.map(async (c) => { try { return await c.send(n); } catch (err) { // A channel that throws instead of returning is a bug in that channel, not a reason to fail // the notification or the producer behind it. console.error(`[notify] channel ${c.name} threw:`, err instanceof Error ? err.message : err); return { channel: c.name, sent: 0, failed: 1 }; } }), ); }