| 43 | } |
| 44 | |
| 45 | export class WebhookController extends BaseController { |
| 46 | private webhooks: Map<string, WebhookConfig> = new Map(); |
| 47 | private webhookDeliveryQueue: WebhookDelivery[] = []; |
| 48 | |
| 49 | constructor(private plugin: TaskNotesPlugin) { |
| 50 | super(); |
| 51 | this.loadWebhooks(); |
| 52 | } |
| 53 | |
| 54 | @Post("/api/webhooks") |
| 55 | async registerWebhook(req: HTTPRequestLike, res: HTTPResponseLike): Promise<void> { |
| 56 | try { |
| 57 | const body = await this.parseRequestBody(req); |
| 58 | const requestBody = isRecord(body) ? body : {}; |
| 59 | |
| 60 | if (typeof requestBody.url !== "string") { |
| 61 | this.sendResponse( |
| 62 | res, |
| 63 | 400, |
| 64 | this.errorResponse("URL is required and must be a string") |
| 65 | ); |
| 66 | return; |
| 67 | } |
| 68 | |
| 69 | if ( |
| 70 | !Array.isArray(requestBody.events) || |
| 71 | requestBody.events.length === 0 || |
| 72 | !requestBody.events.every(isWebhookEvent) |
| 73 | ) { |
| 74 | this.sendResponse( |
| 75 | res, |
| 76 | 400, |
| 77 | this.errorResponse( |
| 78 | "Events array is required and must contain valid webhook events" |
| 79 | ) |
| 80 | ); |
| 81 | return; |
| 82 | } |
| 83 | |
| 84 | // Generate webhook ID and secret if not provided |
| 85 | const id = optionalString(requestBody.id) ?? this.generateWebhookId(); |
| 86 | const secret = optionalString(requestBody.secret) ?? this.generateWebhookSecret(); |
| 87 | |
| 88 | const webhook: WebhookConfig = { |
| 89 | id, |
| 90 | url: requestBody.url, |
| 91 | events: requestBody.events, |
| 92 | secret, |
| 93 | active: requestBody.active !== false, |
| 94 | createdAt: new Date().toISOString(), |
| 95 | failureCount: 0, |
| 96 | successCount: 0, |
| 97 | transformFile: optionalString(requestBody.transformFile), |
| 98 | corsHeaders: requestBody.corsHeaders !== false, // Default to true unless explicitly set to false |
| 99 | }; |
| 100 | |
| 101 | this.webhooks.set(id, webhook); |
| 102 | await this.saveWebhooks(); |