import { nativeImage } from 'electron/common' import type { BrowserWindow as ElectronBrowserWindow } from 'electron/main' import { app } from 'electron/main' import { APP_ICON_DANGER_BASE64, APP_ICON_NORMAL_BASE64, WINDOWS_OVERLAY_ICON_BASE64, } from './brand-icons' import { createRuntimeSnapshot } from './runtime-snapshot' import { updateTrayIssueIndicator } from './tray' type RuntimeSnapshot = ReturnType type AppIconTone = 'normal' | 'danger' type AppIndicatorImage = ReturnType const HEALTH_ISSUE_AUTH_STATES = new Set(['expired', 'revoked', 'challenge_required']) const ISSUE_INDICATOR_REFRESH_MS = 15_000 let refreshTimer: ReturnType | null = null const appIconCache = new Map() let overlayIcon: AppIndicatorImage | null = null function compactCount(count: number): string { return count > 99 ? '99+' : String(count) } function normalizeIssueCount(count: number): number { if (!Number.isFinite(count)) { return 0 } return Math.max(0, Math.floor(count)) } function iconFromBase64(base64: string): AppIndicatorImage { const icon = nativeImage.createFromBuffer(Buffer.from(base64, 'base64')) return icon.isEmpty() ? nativeImage.createEmpty() : icon } export function createDesktopHealthIcon(tone: AppIconTone): AppIndicatorImage { const cached = appIconCache.get(tone) if (cached) { return cached } const icon = iconFromBase64(tone === 'danger' ? APP_ICON_DANGER_BASE64 : APP_ICON_NORMAL_BASE64) appIconCache.set(tone, icon) return icon } function createWindowsOverlayIcon(): AppIndicatorImage { if (overlayIcon) { return overlayIcon } overlayIcon = iconFromBase64(WINDOWS_OVERLAY_ICON_BASE64) return overlayIcon } function healthIssueCount(snapshot: RuntimeSnapshot): number { return snapshot.accounts.filter((account) => HEALTH_ISSUE_AUTH_STATES.has(account.authState)) .length } function applyPlatformAppIndicator(issueCount: number, window: ElectronBrowserWindow | null): void { const safeCount = normalizeIssueCount(issueCount) const hasIssues = safeCount > 0 const icon = createDesktopHealthIcon(hasIssues ? 'danger' : 'normal') updateTrayIssueIndicator(safeCount) if (process.platform === 'darwin') { app.dock?.setIcon(icon) app.dock?.setBadge(hasIssues ? compactCount(safeCount) : '') app.setBadgeCount(safeCount) return } if (process.platform === 'linux') { app.setBadgeCount(safeCount) } if (!window || window.isDestroyed()) { return } window.setIcon(icon) if (process.platform === 'win32') { window.setOverlayIcon( hasIssues ? createWindowsOverlayIcon() : null, hasIssues ? `${safeCount} 个账号健康问题` : '', ) } } export function applyDesktopHealthIndicatorFromSnapshot( snapshot: RuntimeSnapshot, window: ElectronBrowserWindow | null, ): void { applyPlatformAppIndicator(healthIssueCount(snapshot), window) } export function syncDesktopHealthIndicator(window: ElectronBrowserWindow | null): void { try { applyDesktopHealthIndicatorFromSnapshot(createRuntimeSnapshot(), window) } catch (error) { console.warn('[desktop-health-indicator] sync failed', { message: error instanceof Error ? error.message : String(error), }) } } export function startDesktopHealthIndicator(getWindow: () => ElectronBrowserWindow | null): void { if (refreshTimer) { clearInterval(refreshTimer) refreshTimer = null } syncDesktopHealthIndicator(getWindow()) refreshTimer = setInterval(() => { syncDesktopHealthIndicator(getWindow()) }, ISSUE_INDICATOR_REFRESH_MS) refreshTimer.unref?.() }