chore: initial commit - CloudSearch v0.0.2
This commit is contained in:
95
packages/backend/src/cloud/notification.service.ts
Normal file
95
packages/backend/src/cloud/notification.service.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
// 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');
|
||||
}
|
||||
Reference in New Issue
Block a user