jiraCloud/services/webexInbound.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

100 lines
2.5 KiB
JavaScript

import crypto from 'crypto';
import fetch from 'node-fetch';
const DEDUPE_TTL_MS = 24 * 60 * 60 * 1000;
export function verifyWebhookSignature(rawBody, signatureHeader, secret) {
if (!secret) {
return false;
}
if (!signatureHeader || !rawBody) {
return false;
}
const expected = crypto.createHmac('sha1', secret).update(rawBody).digest('hex');
try {
const expectedBuffer = Buffer.from(expected, 'utf8');
const actualBuffer = Buffer.from(signatureHeader, 'utf8');
if (expectedBuffer.length !== actualBuffer.length) {
return false;
}
return crypto.timingSafeEqual(expectedBuffer, actualBuffer);
} catch {
return false;
}
}
export function createMessageDeduper() {
const seen = new Map();
function cleanup() {
const now = Date.now();
for (const [messageId, expiresAt] of seen.entries()) {
if (expiresAt <= now) {
seen.delete(messageId);
}
}
}
function has(messageId) {
cleanup();
return seen.has(messageId);
}
function add(messageId) {
cleanup();
seen.set(messageId, Date.now() + DEDUPE_TTL_MS);
}
return { has, add };
}
export async function fetchWebexMessage(token, messageId) {
const response = await fetch(`https://webexapis.com/v1/messages/${encodeURIComponent(messageId)}`, {
headers: {
Authorization: `Bearer ${token}`,
Accept: 'application/json',
},
});
const responseText = await response.text();
let data;
try {
data = responseText ? JSON.parse(responseText) : {};
} catch {
throw new Error(`Invalid JSON from Webex messages API: ${responseText}`);
}
if (!response.ok) {
const error = new Error(data.message || responseText || `Webex API error (${response.status})`);
error.status = response.status;
throw error;
}
return data;
}
export function extractMessageFromWebhook(body) {
const resource = body?.resource;
const event = body?.event;
const data = body?.data || {};
if (resource !== 'messages' || event !== 'created') {
return { supported: false, reason: 'unsupported_event' };
}
return {
supported: true,
messageId: data.id,
roomId: data.roomId,
personId: data.personId,
personEmail: data.personEmail,
text: data.text,
parentId: data.parentId,
};
}