import fetch from 'node-fetch'; const WEBHOOKS_URL = 'https://webexapis.com/v1/webhooks'; export function findWebhookForRoom(webhooks, { targetUrl, roomId }) { const filter = `roomId=${roomId}`; return (webhooks || []).find(webhook => webhook.targetUrl === targetUrl && webhook.resource === 'messages' && webhook.event === 'created' && webhook.filter === filter ) || null; } export function createWebexWebhookManager({ webexOAuth, log }) { async function listWebhooks(accessToken) { const response = await fetch(WEBHOOKS_URL, { headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json', }, }); const responseText = await response.text(); let data; try { data = responseText ? JSON.parse(responseText) : {}; } catch { throw new Error(`Invalid webhooks list response: ${responseText}`); } if (!response.ok) { const error = new Error(data.message || responseText || 'Failed to list webhooks'); error.status = response.status; throw error; } return data.items || []; } async function createWebhook(accessToken, payload) { const response = await fetch(WEBHOOKS_URL, { method: 'POST', headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }); const responseText = await response.text(); let data; try { data = responseText ? JSON.parse(responseText) : {}; } catch { throw new Error(`Invalid webhook create response: ${responseText}`); } if (!response.ok) { const error = new Error(data.message || responseText || 'Failed to create webhook'); error.status = response.status; error.details = data; throw error; } return data; } async function updateWebhook(accessToken, webhookId, payload) { const response = await fetch(`${WEBHOOKS_URL}/${encodeURIComponent(webhookId)}`, { method: 'PUT', headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }); const responseText = await response.text(); let data; try { data = responseText ? JSON.parse(responseText) : {}; } catch { throw new Error(`Invalid webhook update response: ${responseText}`); } if (!response.ok) { const error = new Error(data.message || responseText || 'Failed to update webhook'); error.status = response.status; error.details = data; throw error; } return data; } async function ensureWebhooks({ accessToken, webexRooms, config }) { if (!accessToken) { throw new Error('Integration access token is required to manage webhooks'); } const targetUrl = config.webex.webhookTargetUrl; const secret = config.webex.webhookSecret; const existingWebhooks = await listWebhooks(accessToken); const results = []; for (const [configKey, room] of Object.entries(webexRooms || {})) { if (room.enabled === false || !room.roomId) { continue; } const filter = `roomId=${room.roomId}`; const payload = { name: `jiracloud-${configKey}`, targetUrl, resource: 'messages', event: 'created', filter, secret, }; let webhook = findWebhookForRoom(existingWebhooks, { targetUrl, roomId: room.roomId }); if (!webhook) { webhook = await createWebhook(accessToken, payload); log.logger('webexWebhookManager', `Created webhook ${webhook.id} for room ${configKey}`); results.push({ configKey, roomId: room.roomId, action: 'created', webhookId: webhook.id }); } else if (webhook.status !== 'active') { webhook = await updateWebhook(accessToken, webhook.id, { status: 'active' }); log.logger('webexWebhookManager', `Reactivated webhook ${webhook.id} for room ${configKey}`); results.push({ configKey, roomId: room.roomId, action: 'reactivated', webhookId: webhook.id }); } else { log.logger('webexWebhookManager', `Webhook already active for room ${configKey} (${webhook.id})`); results.push({ configKey, roomId: room.roomId, action: 'exists', webhookId: webhook.id }); } webexOAuth.setWebhookId(room.roomId, webhook.id); } return results; } return { ensureWebhooks, findWebhookForRoom, listWebhooks, }; }