117 lines
4.0 KiB
JavaScript
117 lines
4.0 KiB
JavaScript
'use strict';
|
|
|
|
const crypto = require('crypto');
|
|
const axios = require('axios');
|
|
const {
|
|
getYydNotificationCenterRuntimeConfig,
|
|
} = require('./yydNotificationCenterSettings');
|
|
|
|
const NOTIFICATION_PATH = '/v1/notifications';
|
|
const DEFAULT_TIMEOUT_MS = 15000;
|
|
|
|
function normalizeText(value, maxLength = 4800) {
|
|
return String(value ?? '').trim().slice(0, maxLength);
|
|
}
|
|
|
|
async function getYydNotificationCenterConfig(options = {}) {
|
|
const config = await getYydNotificationCenterRuntimeConfig(options);
|
|
return {
|
|
baseUrl: normalizeText(config.baseUrl, 1000).replace(/\/+$/, ''),
|
|
projectCode: normalizeText(config.projectCode, 120),
|
|
projectSecret: normalizeText(config.projectSecret, 1000),
|
|
timeoutMs: Math.max(Number(config.timeoutMs) || DEFAULT_TIMEOUT_MS, 3000),
|
|
};
|
|
}
|
|
|
|
function isYydNotificationCenterConfigured(config = {}) {
|
|
return Boolean(config.baseUrl && config.projectCode && config.projectSecret);
|
|
}
|
|
|
|
function buildYydNotificationSignature({
|
|
projectSecret,
|
|
timestamp,
|
|
nonce,
|
|
method = 'POST',
|
|
path = NOTIFICATION_PATH,
|
|
rawBody = '',
|
|
}) {
|
|
const bodyHash = crypto.createHash('sha256').update(rawBody, 'utf8').digest('hex');
|
|
const canonical = `${timestamp}\n${nonce}\n${String(method).toUpperCase()}\n${path}\n${bodyHash}`;
|
|
return crypto.createHmac('sha256', projectSecret).update(canonical, 'utf8').digest('hex');
|
|
}
|
|
|
|
function validateNotificationPayload(input = {}) {
|
|
const payload = {
|
|
idempotencyKey: normalizeText(input.idempotencyKey, 160),
|
|
title: normalizeText(input.title, 100),
|
|
content: normalizeText(input.content, 4800),
|
|
};
|
|
const recipientUserId = normalizeText(input.recipientUserId, 160);
|
|
const recipientAlias = normalizeText(input.recipientAlias, 50);
|
|
if (!payload.idempotencyKey || !payload.title || !payload.content) {
|
|
throw new Error('通知缺少幂等键、标题或正文');
|
|
}
|
|
if (Boolean(recipientUserId) === Boolean(recipientAlias)) {
|
|
throw new Error('通知收件人必须且只能提供 recipientUserId 或 recipientAlias');
|
|
}
|
|
if (recipientUserId) payload.recipientUserId = recipientUserId;
|
|
if (recipientAlias) payload.recipientAlias = recipientAlias;
|
|
const actionTitle = normalizeText(input.actionTitle, 20);
|
|
const actionUrl = normalizeText(input.actionUrl, 2000);
|
|
if (actionTitle) payload.actionTitle = actionTitle;
|
|
if (actionUrl) payload.actionUrl = actionUrl;
|
|
return payload;
|
|
}
|
|
|
|
async function sendYydNotification(input = {}, options = {}) {
|
|
const config = options.config || await getYydNotificationCenterConfig();
|
|
if (!isYydNotificationCenterConfigured(config)) {
|
|
const error = new Error('统一通知中心尚未配置');
|
|
error.code = 'YYD_NOTIFICATION_CENTER_NOT_CONFIGURED';
|
|
throw error;
|
|
}
|
|
|
|
const payload = validateNotificationPayload(input);
|
|
const rawBody = JSON.stringify(payload);
|
|
const timestamp = String(options.timestamp || Math.trunc(Date.now() / 1000));
|
|
const nonce = String(options.nonce || crypto.randomBytes(16).toString('hex'));
|
|
const signature = buildYydNotificationSignature({
|
|
projectSecret: config.projectSecret,
|
|
timestamp,
|
|
nonce,
|
|
rawBody,
|
|
});
|
|
|
|
try {
|
|
const response = await axios.post(`${config.baseUrl}${NOTIFICATION_PATH}`, rawBody, {
|
|
timeout: config.timeoutMs,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'x-yyd-project-code': config.projectCode,
|
|
'x-yyd-timestamp': timestamp,
|
|
'x-yyd-nonce': nonce,
|
|
'x-yyd-signature': signature,
|
|
},
|
|
transformRequest: [(body) => body],
|
|
});
|
|
return response.data;
|
|
} catch (error) {
|
|
const detail = error?.response?.data;
|
|
const wrapped = new Error(
|
|
detail?.message || detail?.error || error.message || '统一通知中心请求失败'
|
|
);
|
|
wrapped.code = detail?.code || error.code || 'YYD_NOTIFICATION_CENTER_REQUEST_FAILED';
|
|
wrapped.status = Number(error?.response?.status || 0) || 502;
|
|
throw wrapped;
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
NOTIFICATION_PATH,
|
|
buildYydNotificationSignature,
|
|
getYydNotificationCenterConfig,
|
|
isYydNotificationCenterConfigured,
|
|
sendYydNotification,
|
|
validateNotificationPayload,
|
|
};
|