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>
100 lines
2.5 KiB
JavaScript
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,
|
|
};
|
|
}
|