Files
crm-ai/bridge/src/libredesk-webhook.js
T

67 lines
2.7 KiB
JavaScript

'use strict';
const crypto = require('crypto');
const prom = require('./prom');
// Phase 0:验签 -> 去重 -> 只处理 contact 的 message.created -> 回一条占位消息。
// Phase 1 会把这里替换成 bridge AI 编排。
function verifySignature(rawBody, secret, signature) {
if (!secret || !signature) return false;
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(String(signature));
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
function createLibreDeskWebhook(cfg, deps = {}) {
const client = deps.client || require('./libredesk').createLibreDeskClient(cfg);
const agent = deps.agent || null;
const seen = new Map();
const TTL_MS = 10 * 60 * 1000;
function markSeen(uuid) {
const now = Date.now();
for (const [key, ts] of seen) if (now - ts > TTL_MS) seen.delete(key);
if (seen.has(uuid)) return false;
seen.set(uuid, now);
return true;
}
async function handle(body) {
if (!body || body.event !== 'message.created') return;
const msg = body.payload || {};
prom.inc('bridge_webhook_events_total', { event: body.event || 'unknown' });
if (msg.sender_type !== 'contact' || msg.private) return;
if (!msg.uuid || !msg.conversation_uuid) return;
if (!markSeen(msg.uuid)) return;
const conv = await client.getConversation(msg.conversation_uuid);
const inboxId = String(conv && conv.inbox_id);
const allowed = cfg.libredeskWebhookInboxIds || [];
if (allowed.length && !allowed.includes(inboxId)) {
console.log(JSON.stringify({ ts: new Date().toISOString(), webhook: 'ignored_inbox', inbox: inboxId, conversation: msg.conversation_uuid }));
return;
}
const text = String(msg.text_content || msg.content || '').trim();
console.log(JSON.stringify({ ts: new Date().toISOString(), webhook: 'message.created', inbox: inboxId, conversation: msg.conversation_uuid, message: msg.uuid, text: text.slice(0, 120) }));
if (agent) {
try {
await agent.run(conv, msg);
} catch (err) {
console.error(JSON.stringify({ ts: new Date().toISOString(), webhook: 'agent_error', conversation: msg.conversation_uuid, error: err.message }));
}
} else {
// 没有 agent 时的占位回复(仅用于链路调试)
const reply = `Bridge Phase 0 已收到:${text || '(空消息)'}`;
await client.sendMessage(msg.conversation_uuid, reply, { echoId: `bridge-p0-${msg.uuid}` });
console.log(JSON.stringify({ ts: new Date().toISOString(), webhook: 'replied', conversation: msg.conversation_uuid, reply }));
}
}
return { verifySignature, handle };
}
module.exports = { createLibreDeskWebhook, verifySignature };