This repository has no description
1import { authedGet, authedPost, type AppviewContext } from "./appview";
2import type { XrpcRequestInit } from "./client";
3
4// canonical event names the appview dispatches on (models.WebhookEvent*). the
5// values are the wire format; labels are a UI concern.
6export const WEBHOOK_EVENTS = [
7 { id: "push", label: "Push events" },
8 { id: "repository:renamed", label: "Repository renamed" },
9 { id: "pull_request:created", label: "Pull request opened" },
10 { id: "pull_request:resubmitted", label: "Pull request resubmitted" },
11 { id: "pull_request:merged", label: "Pull request merged" },
12 { id: "pull_request:closed", label: "Pull request closed" },
13 { id: "pull_request:reopened", label: "Pull request reopened" }
14] as const;
15
16export type WebhookEvent = (typeof WEBHOOK_EVENTS)[number]["id"];
17
18// mirrors org.tangled.temp.repo.listWebhooks#webhook
19export interface Webhook {
20 id: number;
21 url: string;
22 active: boolean;
23 events: string[];
24 createdAt: string;
25 updatedAt?: string;
26}
27
28// mirrors org.tangled.temp.repo.listWebhookDeliveries#delivery
29export interface WebhookDelivery {
30 id: number;
31 deliveryId: string;
32 event: string;
33 url: string;
34 success: boolean;
35 createdAt: string;
36 requestBody?: string;
37 responseBody?: string;
38 responseCode?: number;
39}
40
41export interface CreateWebhookInput {
42 url: string;
43 events: string[];
44 secret?: string;
45 active?: boolean;
46}
47
48export interface UpdateWebhookInput {
49 url?: string;
50 events?: string[];
51 secret?: string;
52 active?: boolean;
53}
54
55const LIST = "org.tangled.temp.repo.listWebhooks";
56const CREATE = "org.tangled.temp.repo.createWebhook";
57const UPDATE = "org.tangled.temp.repo.updateWebhook";
58const DELETE = "org.tangled.temp.repo.deleteWebhook";
59const TOGGLE = "org.tangled.temp.repo.toggleWebhook";
60const LIST_DELIVERIES = "org.tangled.temp.repo.listWebhookDeliveries";
61const RETRY_DELIVERY = "org.tangled.temp.repo.retryWebhookDelivery";
62
63export const listWebhooks = async (
64 ctx: AppviewContext,
65 repoDid: string,
66 init?: XrpcRequestInit
67): Promise<Webhook[]> => {
68 const res = await authedGet<{ webhooks?: Webhook[] }>(ctx, LIST, { repoDid }, init);
69 return res.webhooks ?? [];
70};
71
72export const createWebhook = async (
73 ctx: AppviewContext,
74 repoDid: string,
75 input: CreateWebhookInput,
76 init?: XrpcRequestInit
77): Promise<number | undefined> => {
78 const res = await authedPost<{ id: number }>(ctx, CREATE, { repoDid, ...input }, init);
79 return res?.id;
80};
81
82export const updateWebhook = (
83 ctx: AppviewContext,
84 repoDid: string,
85 id: number,
86 patch: UpdateWebhookInput,
87 init?: XrpcRequestInit
88): Promise<void> =>
89 authedPost(ctx, UPDATE, { repoDid, id, ...patch }, init).then(() => undefined);
90
91export const deleteWebhook = (
92 ctx: AppviewContext,
93 repoDid: string,
94 id: number,
95 init?: XrpcRequestInit
96): Promise<void> => authedPost(ctx, DELETE, { repoDid, id }, init).then(() => undefined);
97
98// flips active state server-side; returns the new value.
99export const toggleWebhook = async (
100 ctx: AppviewContext,
101 repoDid: string,
102 id: number,
103 init?: XrpcRequestInit
104): Promise<boolean | undefined> => {
105 const res = await authedPost<{ active: boolean }>(ctx, TOGGLE, { repoDid, id }, init);
106 return res?.active;
107};
108
109export const listWebhookDeliveries = async (
110 ctx: AppviewContext,
111 repoDid: string,
112 id: number,
113 limit?: number,
114 init?: XrpcRequestInit
115): Promise<WebhookDelivery[]> => {
116 const res = await authedGet<{ deliveries?: WebhookDelivery[] }>(
117 ctx,
118 LIST_DELIVERIES,
119 { repoDid, id, limit },
120 init
121 );
122 return res.deliveries ?? [];
123};
124
125export const retryWebhookDelivery = (
126 ctx: AppviewContext,
127 repoDid: string,
128 webhookId: number,
129 deliveryId: string,
130 init?: XrpcRequestInit
131): Promise<void> =>
132 authedPost(ctx, RETRY_DELIVERY, { repoDid, webhookId, deliveryId }, init).then(() => undefined);