import { ok } from "@atcute/client"; import type { Did, Nsid } from "@atcute/lexicons/syntax"; import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; import { serviceClient } from "$lib/api/_request"; import { serviceDidForHost } from "$lib/auth/agent"; import type { Permission } from "$lib/auth/scopes"; import { mainSchema as createWebhookSchema } from "$lib/api/lexicons/types/org/tangled/temp/webhook/createWebhook"; import { mainSchema as deleteWebhookSchema } from "$lib/api/lexicons/types/org/tangled/temp/webhook/deleteWebhook"; import { mainSchema as getDeliveriesSchema, type Delivery } from "$lib/api/lexicons/types/org/tangled/temp/webhook/getDeliveriesForWebhooks"; import { mainSchema as listWebhooksSchema, type Webhook } from "$lib/api/lexicons/types/org/tangled/temp/webhook/listWebhooks"; import { mainSchema as retryDeliverySchema } from "$lib/api/lexicons/types/org/tangled/temp/webhook/retryDelivery"; import { mainSchema as toggleWebhookSchema } from "$lib/api/lexicons/types/org/tangled/temp/webhook/toggleWebhook"; import { mainSchema as updateWebhookSchema } from "$lib/api/lexicons/types/org/tangled/temp/webhook/updateWebhook"; import { didOf } from "$lib/api/syntax"; export type { Delivery, Webhook }; export const WEBHOOK_EVENTS = [ { id: "push", label: "Push events" }, { id: "repository:renamed", label: "Repository renamed" }, { id: "pull_request:created", label: "Pull request opened" }, { id: "pull_request:resubmitted", label: "Pull request resubmitted" }, { id: "pull_request:merged", label: "Pull request merged" }, { id: "pull_request:closed", label: "Pull request closed" }, { id: "pull_request:reopened", label: "Pull request reopened" } ] as const; export type WebhookEvent = (typeof WEBHOOK_EVENTS)[number]["id"]; export type WebhookEventSelection = Record; export type WebhookActivation = "active" | "inactive"; export const webhookActivation = (active: boolean): WebhookActivation => active ? "active" : "inactive"; export const webhookIsActive = (activation: WebhookActivation): boolean => activation === "active"; const WEBHOOK_METHODS = [ createWebhookSchema.nsid, deleteWebhookSchema.nsid, getDeliveriesSchema.nsid, listWebhooksSchema.nsid, retryDeliverySchema.nsid, toggleWebhookSchema.nsid, updateWebhookSchema.nsid ] as const; export const webhookPermissions = (spindle: URL): readonly Permission[] => { const aud = didOf(serviceDidForHost(spindle.host)); return WEBHOOK_METHODS.map((lxm) => ({ resource: "rpc", lxm: lxm as Nsid, aud })); }; export const webhookEventSelection = ( events: readonly string[] = ["push"] ): WebhookEventSelection => Object.fromEntries( WEBHOOK_EVENTS.map(({ id }) => [id, events.includes(id)]) ) as WebhookEventSelection; export const selectedWebhookEvents = (selection: WebhookEventSelection): WebhookEvent[] => WEBHOOK_EVENTS.flatMap(({ id }) => (selection[id] ? [id] : [])); export const isWebhookUrl = (value: string): boolean => { try { return ["http:", "https:"].includes(new URL(value.trim()).protocol); } catch { return false; } }; type GenericUri = `${string}:${string}`; const client = (agent: OAuthUserAgent, spindle: URL) => serviceClient(agent, spindle.toString()).xrpc; const url = (value: string): GenericUri => { const normalized = value.trim(); if (!isWebhookUrl(normalized)) throw new Error("Webhook URL must use HTTP or HTTPS."); return normalized as GenericUri; }; export const listWebhooks = async ( agent: OAuthUserAgent, spindle: URL, repoDid: Did ): Promise => { const { webhooks } = await ok( client(agent, spindle).call(listWebhooksSchema, { params: { repoDid } }) ); return webhooks ?? []; }; export interface CreateWebhook { url: string; events: WebhookEvent[]; secret?: string; active?: boolean; } export const createWebhook = async ( agent: OAuthUserAgent, spindle: URL, repoDid: Did, webhook: CreateWebhook ): Promise => { const { id } = await ok( client(agent, spindle).call(createWebhookSchema, { input: { ...webhook, repoDid, url: url(webhook.url) } }) ); return id; }; export interface UpdateWebhook { url?: string; events?: WebhookEvent[]; secret?: string; active?: boolean; } export const updateWebhook = async ( agent: OAuthUserAgent, spindle: URL, repoDid: Did, id: number, webhook: UpdateWebhook ): Promise => { const { url: webhookUrl, ...changes } = webhook; await ok( client(agent, spindle).call(updateWebhookSchema, { input: { ...changes, repoDid, id, ...(webhookUrl === undefined ? {} : { url: url(webhookUrl) }) } }) ); }; export const deleteWebhook = async ( agent: OAuthUserAgent, spindle: URL, repoDid: Did, id: number ): Promise => { await ok( client(agent, spindle).call(deleteWebhookSchema, { input: { repoDid, id } }) ); }; export const toggleWebhook = async ( agent: OAuthUserAgent, spindle: URL, repoDid: Did, id: number ): Promise => { const { active } = await ok( client(agent, spindle).call(toggleWebhookSchema, { input: { repoDid, id } }) ); return active; }; export const listWebhookDeliveries = async ( agent: OAuthUserAgent, spindle: URL, repoDid: Did, ids: number[], limit = 100 ): Promise => { const { deliveries } = await ok( client(agent, spindle).call(getDeliveriesSchema, { params: { repoDid, ids, limit } }) ); return deliveries ?? []; }; export const retryWebhookDelivery = async ( agent: OAuthUserAgent, spindle: URL, repoDid: Did, webhookId: number, deliveryId: string ): Promise => { await ok( client(agent, spindle).call(retryDeliverySchema, { input: { repoDid, webhookId, deliveryId } }) ); };