jiraCloud/services/webexWebhookManager.js
jmcqueen efc64a227c Add Webex inbound OAuth flow and room-to-Jira job request pipeline.
Introduces integration-based webhook registration, message parsing, dry-run
monitoring, JSM ticket creation, and OAuth token refresh for DC Ops spaces.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 08:27:52 -04:00

153 lines
5.1 KiB
JavaScript

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,
};
}