182 lines · 6.6 KB
import { env } from "cloudflare:workers";
import { all, first, run } from "../db";
import { hmacSha256Hex, randomId } from "../crypto";
import { issueParticipants } from "../issues";
import { CAPS } from "../limits";
import type { NotifyMessage, QueueMessage, WebhookEvent } from "./emit";

const DELIVERY_TIMEOUT_MS = 10_000;
const MAX_RESPONSE_BODY = 4096;
const BASE_RETRY_SECONDS = 15;
const MAX_RETRY_SECONDS = 3600;
const HTTP_OK_MIN = 200;
const HTTP_OK_MAX = 300;
const MENTION = /(?:^|[^\w@])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,38}))/g;

type WebhookRow = { id: string; repo_id: string; url: string; secret: string | null; events: string; active: number };

async function dispatch(message: Extract<QueueMessage, { kind: "webhook.dispatch" }>): Promise<void> {
  const hooks = await all<WebhookRow>("SELECT * FROM webhooks WHERE repo_id = ? AND active = 1", message.repoId);
  const deliveries: QueueMessage[] = [];
  for (const hook of hooks) {
    const events: string[] = JSON.parse(hook.events);
    if (!events.includes("*") && !events.includes(message.event)) continue;
    deliveries.push({
      kind: "webhook.deliver",
      webhookId: hook.id,
      event: message.event,
      deliveryId: randomId(),
      payload: message.payload,
    });
  }
  if (deliveries.length > 0) await env.EVENTS.sendBatch(deliveries.map((body) => ({ body })));
}

async function readPrefix(response: Response, limit: number): Promise<string> {
  if (!response.body) return "";
  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let text = "";
  while (text.length < limit) {