全新出发
This commit is contained in:
1 parent
b06c117b1e
commit
fa4edebed6
463 files changed
+181610
-125
No files matched your search
@@ -0,0 +1,186 @@
|
||||
import { Notification } from "electron";
|
||||
import { avatarFileCache, AvatarFileCacheService } from "./avatarFileCacheService";
|
||||
|
||||
export interface LinuxNotificationData {
|
||||
sessionId?: string;
|
||||
title: string;
|
||||
content: string;
|
||||
avatarUrl?: string;
|
||||
channel?: string;
|
||||
insightRecordId?: string;
|
||||
targetRoute?: string;
|
||||
expireTimeout?: number;
|
||||
}
|
||||
|
||||
type NotificationCallback = (payload: unknown) => void;
|
||||
|
||||
let notificationCallbacks: NotificationCallback[] = [];
|
||||
let notificationCounter = 1;
|
||||
const activeNotifications: Map<number, Notification> = new Map();
|
||||
const closeTimers: Map<number, NodeJS.Timeout> = new Map();
|
||||
|
||||
function nextNotificationId(): number {
|
||||
const id = notificationCounter;
|
||||
notificationCounter += 1;
|
||||
return id;
|
||||
}
|
||||
|
||||
function clearNotificationState(notificationId: number): void {
|
||||
activeNotifications.delete(notificationId);
|
||||
const timer = closeTimers.get(notificationId);
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
closeTimers.delete(notificationId);
|
||||
}
|
||||
}
|
||||
|
||||
function triggerNotificationCallback(payload: unknown): void {
|
||||
for (const callback of notificationCallbacks) {
|
||||
try {
|
||||
callback(payload);
|
||||
} catch (error) {
|
||||
console.error("[LinuxNotification] Callback error:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function showLinuxNotification(
|
||||
data: LinuxNotificationData,
|
||||
): Promise<number | null> {
|
||||
if (process.platform !== "linux") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!Notification.isSupported()) {
|
||||
console.warn("[LinuxNotification] Notification API is not supported");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
let iconPath: string | undefined;
|
||||
if (data.avatarUrl) {
|
||||
iconPath = (await avatarFileCache.getAvatarPath(data.avatarUrl)) || undefined;
|
||||
}
|
||||
|
||||
const notification = new Notification({
|
||||
title: data.title,
|
||||
body: data.content,
|
||||
icon: iconPath,
|
||||
});
|
||||
|
||||
const notificationId = nextNotificationId();
|
||||
activeNotifications.set(notificationId, notification);
|
||||
|
||||
notification.on("click", () => {
|
||||
if (data.channel === "ai-insight" && data.insightRecordId) {
|
||||
triggerNotificationCallback({
|
||||
sessionId: data.sessionId,
|
||||
channel: data.channel,
|
||||
insightRecordId: data.insightRecordId,
|
||||
targetRoute: data.targetRoute,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (data.sessionId) {
|
||||
triggerNotificationCallback(data.sessionId);
|
||||
}
|
||||
});
|
||||
|
||||
notification.on("close", () => {
|
||||
clearNotificationState(notificationId);
|
||||
});
|
||||
|
||||
notification.on("failed", (_, error) => {
|
||||
console.error("[LinuxNotification] Notification failed:", error);
|
||||
clearNotificationState(notificationId);
|
||||
});
|
||||
|
||||
const expireTimeout = data.expireTimeout ?? 5000;
|
||||
if (expireTimeout > 0) {
|
||||
const timer = setTimeout(() => {
|
||||
const currentNotification = activeNotifications.get(notificationId);
|
||||
if (currentNotification) {
|
||||
currentNotification.close();
|
||||
}
|
||||
}, expireTimeout);
|
||||
closeTimers.set(notificationId, timer);
|
||||
}
|
||||
|
||||
notification.show();
|
||||
|
||||
console.log(
|
||||
`[LinuxNotification] Shown notification ${notificationId}: ${data.title}`,
|
||||
);
|
||||
|
||||
return notificationId;
|
||||
} catch (error) {
|
||||
console.error("[LinuxNotification] Failed to show notification:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function closeLinuxNotification(
|
||||
notificationId: number,
|
||||
): Promise<void> {
|
||||
const notification = activeNotifications.get(notificationId);
|
||||
if (!notification) return;
|
||||
notification.close();
|
||||
clearNotificationState(notificationId);
|
||||
}
|
||||
|
||||
export async function getCapabilities(): Promise<string[]> {
|
||||
if (process.platform !== "linux") {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!Notification.isSupported()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return ["native-notification", "click"];
|
||||
}
|
||||
|
||||
export function onNotificationAction(callback: NotificationCallback): void {
|
||||
notificationCallbacks.push(callback);
|
||||
}
|
||||
|
||||
export function removeNotificationCallback(
|
||||
callback: NotificationCallback,
|
||||
): void {
|
||||
const index = notificationCallbacks.indexOf(callback);
|
||||
if (index > -1) {
|
||||
notificationCallbacks.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
export async function initLinuxNotificationService(): Promise<void> {
|
||||
if (process.platform !== "linux") {
|
||||
console.log("[LinuxNotification] Not on Linux, skipping init");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Notification.isSupported()) {
|
||||
console.warn("[LinuxNotification] Notification API is not supported");
|
||||
return;
|
||||
}
|
||||
|
||||
const caps = await getCapabilities();
|
||||
console.log("[LinuxNotification] Service initialized with native API:", caps);
|
||||
}
|
||||
|
||||
export async function shutdownLinuxNotificationService(): Promise<void> {
|
||||
// 清理所有活动的通知
|
||||
for (const [id, notification] of activeNotifications) {
|
||||
try {
|
||||
notification.close();
|
||||
} catch {}
|
||||
clearNotificationState(id);
|
||||
}
|
||||
|
||||
// 清理头像文件缓存
|
||||
try {
|
||||
await avatarFileCache.clearCache();
|
||||
} catch {}
|
||||
|
||||
console.log("[LinuxNotification] Service shutdown complete");
|
||||
}
|
||||
Reference in new issue
Block a user