Files
CloudSearch/packages/backend/src/cloud/notification.service.ts

96 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Native fetch available in Node 20+
import { getSystemConfig } from '../admin/system-config.service';
type NotifyLevel = 'info' | 'warn' | 'error';
interface NotifyChannel {
send(title: string, content: string, level: NotifyLevel): Promise<void>;
}
// ---- Feishu Webhook Channel ----
class FeishuChannel implements NotifyChannel {
private webhookUrl: string;
constructor(webhookUrl: string) {
this.webhookUrl = webhookUrl;
}
async send(title: string, content: string, _level: NotifyLevel): Promise<void> {
try {
const body = JSON.stringify({
msg_type: 'interactive',
card: {
header: {
title: { tag: 'plain_text', content: title },
template: _level === 'error' ? 'red' : _level === 'warn' ? 'orange' : 'blue',
},
elements: [
{ tag: 'div', text: { tag: 'lark_md', content } },
{
tag: 'note',
elements: [
{ tag: 'plain_text', content: `CloudSearch · ${new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })}` },
],
},
],
},
});
const resp = await fetch(this.webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body,
});
if (!resp.ok) {
console.error(`[Notify] Feishu send failed: ${resp.status}`);
}
} catch (err: any) {
console.error('[Notify] Feishu send error:', err.message);
}
}
}
// ---- Notification Manager ----
let _channel: NotifyChannel | null = null;
function getChannel(): NotifyChannel | null {
const feishuUrl = process.env.FEISHU_WEBHOOK || getSystemConfig('feishu_webhook_url');
if (!feishuUrl) return null;
if (!_channel) {
_channel = new FeishuChannel(feishuUrl);
console.log('[Notify] Feishu webhook configured');
}
return _channel;
}
/**
* Send a notification through configured channels.
* Returns immediately — failures are logged silently.
*/
export function notify(title: string, content: string, level: NotifyLevel = 'info'): void {
const ch = getChannel();
if (!ch) return;
// Fire-and-forget — don't block the caller
ch.send(title, content, level).catch(() => {});
}
/**
* Notify on critical events:
* - Cookie expired / login failed
* - Save/transfer failed repeatedly
* - Storage below threshold
*/
export function notifyError(title: string, detail: string): void {
notify(`⚠️ ${title}`, detail, 'error');
}
export function notifyWarn(title: string, detail: string): void {
notify(`🔔 ${title}`, detail, 'warn');
}
export function notifyInfo(title: string, detail: string): void {
notify(` ${title}`, detail, 'info');
}