import { logWarn } from '../../utils/logger.js' import type { JsonObject } from '../../types/json.js' import { getNotificationConfig, listEnabledBarkRecipients, listEnabledWpushRecipients, } from './config-service.js' import { sendBarkNotification } from './bark-service.js' import { sendWpushNotification } from './wpush-service.js' type NotificationResult = ReturnType type NotificationInput = { title?: string body?: string category?: string url?: string } export async function sendInternalNotification(input: NotificationInput = {}) { const config = getNotificationConfig() const bark = (config.channels?.bark || {}) as { serverUrl?: string } const barkRecipients = listEnabledBarkRecipients(config) const wpushRecipients = listEnabledWpushRecipients(config) const title = String(input.title || '订单系统通知').trim() || '订单系统通知' const body = String(input.body || '').trim() const category = String(input.category || 'system').trim() || 'system' if (config.enabled === false) { return { enabled: false, channel: 'internal', successCount: 0, failedCount: 0, skippedCount: barkRecipients.length + wpushRecipients.length, results: [] as NotificationResult[], } } const results = [ ...(await Promise.all(barkRecipients.map(async (recipient: JsonObject) => { try { const result = await sendBarkNotification({ recipient, title, body, group: `订单系统/${category}`, ...(bark.serverUrl !== undefined ? { serverUrl: bark.serverUrl } : {}), ...(input.url !== undefined ? { url: input.url } : {}), }) return mapNotificationResult('bark', recipient, recipient.deviceKey, true, '', result.status, result.response) } catch (error) { logWarn('[notification/bark]', 'Bark 内部通知发送失败', { recipientId: recipient.id, recipientName: recipient.name, error, }) return mapNotificationResult( 'bark', recipient, recipient.deviceKey, false, error instanceof Error ? error.message : 'Bark 内部通知发送失败', isPlainObject(error) ? Number(error.statusCode || 0) || 0 : 0, isPlainObject(error) ? error.context || null : null, ) } }))), ...(await Promise.all(wpushRecipients.map(async (recipient: JsonObject) => { try { const result = await sendWpushNotification({ recipient, title, body, }) return mapNotificationResult('wpush', recipient, recipient.apiKey, true, '', result.status, result.response) } catch (error) { logWarn('[notification/wpush]', 'WPush 内部通知发送失败', { recipientId: recipient.id, recipientName: recipient.name, error, }) return mapNotificationResult( 'wpush', recipient, recipient.apiKey, false, error instanceof Error ? error.message : 'WPush 内部通知发送失败', isPlainObject(error) ? Number(error.statusCode || 0) || 0 : 0, isPlainObject(error) ? error.context || null : null, ) } }))), ] return { enabled: true, channel: 'internal', successCount: results.filter((item) => item.ok).length, failedCount: results.filter((item) => !item.ok).length, skippedCount: 0, results, } } function mapNotificationResult( channel: string, recipient: JsonObject, recipientKey: unknown, ok: boolean, errorMessage: string, status: number, response: unknown, ) { return { channel, recipientId: String(recipient.id || '').trim(), recipientName: String(recipient.name || '').trim(), recipientKeyMasked: maskSecretKey(recipientKey), ok, status, errorMessage, response, } } function maskSecretKey(value: unknown) { const normalized = String(value || '').trim() if (!normalized) { return '' } if (normalized.length <= 10) { return `${normalized.slice(0, 2)}****${normalized.slice(-2)}` } return `${normalized.slice(0, 6)}****${normalized.slice(-6)}` } function isPlainObject(value: unknown): value is JsonObject { return Boolean(value) && typeof value === 'object' && !Array.isArray(value) }