import {
  firstHeader,
  verifyHmacSignature,
  verifySlackSignature,
  verifyStripeSignature
} from "../chunk-2TW2QKGZ.js";
import "../chunk-PZ5AY32C.js";

// src/webhooks/router.ts
var WebhookRouter = class {
  providers;
  deliver;
  resolveSecret;
  idempotency;
  idempotencyTtlMs;
  onError;
  nowFn;
  constructor(opts) {
    this.providers = new Map(opts.providers.map((p) => [p.id, p]));
    this.deliver = opts.deliver;
    this.resolveSecret = opts.resolveSecret;
    this.idempotency = opts.idempotency;
    this.idempotencyTtlMs = opts.idempotencyTtlMs ?? 7 * 24 * 60 * 60 * 1e3;
    this.onError = opts.onError ?? defaultOnError;
    this.nowFn = opts.now ?? Date.now;
  }
  /** Process one inbound webhook request. Pure with respect to side-
   *  effects on the router instance — safe to call concurrently. */
  async handle(request) {
    const provider = this.providers.get(request.providerId);
    if (!provider) {
      return { status: 404, body: { error: "unknown_provider", provider: request.providerId } };
    }
    const secret = await this.resolveSecret(provider.id, request.headers);
    if (!secret) {
      return { status: 401, body: { error: "missing_secret", provider: provider.id } };
    }
    const verification = provider.verifySignature({
      rawBody: request.rawBody,
      headers: request.headers,
      secret
    });
    if (!verification.valid) {
      return { status: 401, body: { error: "invalid_signature", reason: verification.reason } };
    }
    let events;
    try {
      events = await provider.parse({ rawBody: request.rawBody, headers: request.headers, now: this.nowFn() });
    } catch (err) {
      this.onError(err, { provider: provider.id });
      return { status: 400, body: { error: "parse_error", message: errMessage(err) } };
    }
    const accepted = [];
    for (const event of events) {
      if (event.providerEventId && this.idempotency) {
        const already = await this.idempotency.seen(event.providerEventId);
        if (already) continue;
      }
      accepted.push(event);
    }
    queueMicrotask(() => {
      void this.deliverEach(accepted);
    });
    return { status: 200, body: { received: accepted.length, total: events.length } };
  }
  async deliverEach(events) {
    for (const event of events) {
      try {
        await this.deliver(event);
        if (event.providerEventId && this.idempotency) {
          await this.idempotency.remember(event.providerEventId, this.idempotencyTtlMs);
        }
      } catch (err) {
        this.onError(err, {
          provider: event.provider,
          eventType: event.eventType,
          providerEventId: event.providerEventId
        });
      }
    }
  }
};
function defaultOnError(err, context) {
  console.error("[WebhookRouter]", context, err);
}
function errMessage(err) {
  return err instanceof Error ? err.message : String(err);
}

// src/webhooks/providers.ts
import { createHmac, timingSafeEqual } from "crypto";
var stripeWebhookProvider = {
  id: "stripe",
  verifySignature({ rawBody, headers, secret }) {
    const sig = firstHeader(headers, "stripe-signature");
    if (!sig) return { valid: false, reason: "missing_stripe_signature" };
    return verifyStripeSignature(rawBody, sig, secret) ? { valid: true } : { valid: false, reason: "invalid_signature" };
  },
  parse({ rawBody, headers, now }) {
    const evt = safeJson(rawBody);
    if (!evt || typeof evt !== "object") return [];
    const e = evt;
    return [
      {
        provider: "stripe",
        eventType: typeof e.type === "string" ? e.type : "stripe.unknown",
        providerEventId: typeof e.id === "string" ? e.id : void 0,
        receivedAt: now ?? Date.now(),
        payload: evt,
        headers: normalizeHeaders(headers)
      }
    ];
  }
};
var slackWebhookProvider = {
  id: "slack",
  verifySignature({ rawBody, headers, secret }) {
    const sig = firstHeader(headers, "x-slack-signature");
    const ts = firstHeader(headers, "x-slack-request-timestamp");
    if (!sig || !ts) return { valid: false, reason: "missing_slack_signature_or_timestamp" };
    return verifySlackSignature(rawBody, sig, ts, secret) ? { valid: true } : { valid: false, reason: "invalid_signature" };
  },
  parse({ rawBody, headers, now }) {
    const evt = safeJson(rawBody);
    if (!evt || typeof evt !== "object") return [];
    if (evt.type === "url_verification") {
      return [{
        provider: "slack",
        eventType: "slack.url_verification",
        receivedAt: now ?? Date.now(),
        payload: evt,
        headers: normalizeHeaders(headers)
      }];
    }
    const eventType = `slack.${evt.event?.type ?? evt.type ?? "unknown"}`;
    return [{
      provider: "slack",
      eventType,
      providerEventId: typeof evt.event_id === "string" ? evt.event_id : void 0,
      receivedAt: now ?? Date.now(),
      payload: evt,
      headers: normalizeHeaders(headers)
    }];
  }
};
var docusealWebhookProvider = {
  id: "docuseal",
  verifySignature({ rawBody, headers, secret }) {
    const sig = firstHeader(headers, "x-docuseal-signature");
    if (!sig) return { valid: false, reason: "missing_docuseal_signature" };
    const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
    const a = Buffer.from(sig.toLowerCase(), "utf-8");
    const b = Buffer.from(expected, "utf-8");
    if (a.length !== b.length) return { valid: false, reason: "invalid_signature" };
    return timingSafeEqual(a, b) ? { valid: true } : { valid: false, reason: "invalid_signature" };
  },
  parse({ rawBody, headers, now }) {
    const evt = safeJson(rawBody);
    if (!evt || typeof evt !== "object") return [];
    return [{
      provider: "docuseal",
      eventType: `docuseal.${evt.event_type ?? "unknown"}`,
      providerEventId: typeof evt.event_id === "string" ? evt.event_id : void 0,
      receivedAt: now ?? Date.now(),
      payload: evt,
      headers: normalizeHeaders(headers)
    }];
  }
};
var gmailWebhookProvider = {
  id: "gmail",
  verifySignature({ headers, secret }) {
    const auth = firstHeader(headers, "authorization");
    if (!auth) return { valid: false, reason: "missing_authorization" };
    const m = /^(?:Bearer|Token)\s+(.+)$/i.exec(auth);
    if (!m) return { valid: false, reason: "invalid_authorization_format" };
    const a = Buffer.from(m[1], "utf-8");
    const b = Buffer.from(secret, "utf-8");
    if (a.length !== b.length) return { valid: false, reason: "invalid_signature" };
    return timingSafeEqual(a, b) ? { valid: true } : { valid: false, reason: "invalid_signature" };
  },
  parse({ rawBody, headers, now }) {
    const envelope = safeJson(rawBody);
    if (!envelope?.message?.data) return [];
    let payload;
    try {
      payload = JSON.parse(Buffer.from(envelope.message.data, "base64").toString("utf-8"));
    } catch {
      return [];
    }
    const inner = payload;
    return [{
      provider: "gmail",
      eventType: "gmail.history_changed",
      providerEventId: envelope.message.messageId,
      receivedAt: now ?? Date.now(),
      payload: { ...inner, publishTime: envelope.message.publishTime },
      headers: normalizeHeaders(headers)
    }];
  }
};
var gdriveWebhookProvider = {
  id: "gdrive",
  verifySignature({ headers, secret }) {
    const token = firstHeader(headers, "x-goog-channel-token");
    if (!token) return { valid: false, reason: "missing_channel_token" };
    const a = Buffer.from(token, "utf-8");
    const b = Buffer.from(secret, "utf-8");
    if (a.length !== b.length) return { valid: false, reason: "invalid_signature" };
    return timingSafeEqual(a, b) ? { valid: true } : { valid: false, reason: "invalid_signature" };
  },
  parse({ headers, now }) {
    const resourceId = firstHeader(headers, "x-goog-resource-id");
    const resourceState = firstHeader(headers, "x-goog-resource-state") ?? "unknown";
    const channelId = firstHeader(headers, "x-goog-channel-id");
    const messageNumber = firstHeader(headers, "x-goog-message-number");
    if (resourceState === "sync") {
      return [{
        provider: "gdrive",
        eventType: "gdrive.channel.sync",
        providerEventId: messageNumber ? `${channelId}-${messageNumber}` : void 0,
        receivedAt: now ?? Date.now(),
        payload: { channelId, resourceId, resourceState },
        headers: normalizeHeaders(headers)
      }];
    }
    return [{
      provider: "gdrive",
      eventType: `gdrive.resource.${resourceState}`,
      providerEventId: messageNumber ? `${channelId}-${messageNumber}` : void 0,
      receivedAt: now ?? Date.now(),
      payload: { channelId, resourceId, resourceState },
      headers: normalizeHeaders(headers)
    }];
  }
};
function genericHmacWebhookProvider(options) {
  const header = options.signatureHeader ?? "x-signature";
  return {
    id: options.id,
    verifySignature({ rawBody, headers, secret }) {
      const sig = firstHeader(headers, header);
      if (!sig) return { valid: false, reason: `missing_${header}` };
      return verifyHmacSignature(rawBody, sig, secret, {
        algorithm: options.algorithm ?? "sha256",
        signaturePrefix: options.signaturePrefix
      }) ? { valid: true } : { valid: false, reason: "invalid_signature" };
    },
    parse: options.parse ?? (({ rawBody, headers, now }) => {
      const evt = safeJson(rawBody) ?? rawBody;
      return [{
        provider: options.id,
        eventType: `${options.id}.event`,
        receivedAt: now ?? Date.now(),
        payload: evt,
        headers: normalizeHeaders(headers)
      }];
    })
  };
}
function safeJson(s) {
  try {
    return JSON.parse(s);
  } catch {
    return null;
  }
}
function normalizeHeaders(headers) {
  const out = {};
  for (const [k, v] of Object.entries(headers)) {
    if (v === void 0) continue;
    const value = Array.isArray(v) ? v[0] : v;
    if (typeof value === "string") out[k.toLowerCase()] = value;
  }
  return out;
}
export {
  WebhookRouter,
  docusealWebhookProvider,
  gdriveWebhookProvider,
  genericHmacWebhookProvider,
  gmailWebhookProvider,
  slackWebhookProvider,
  stripeWebhookProvider
};
//# sourceMappingURL=index.js.map