162abdc97c
- Add Prettier 3 with prettier-plugin-organize-imports (sorts/removes unused imports) - Add ESLint 10 flat config with typescript-eslint + eslint-plugin-vue + eslint-config-prettier - Add root scripts: format, format:check, lint, lint:fix - Reformat 257 files across admin-web, ops-web, desktop-client, packages - Remove unused locals/exports flagged by --noUnusedLocals/--noUnusedParameters - Fix duplicate localTabLabel key, surrogate-pair regex u-flag, NBSP literal in regex - Skip server/ (Go) and apps/browser-extension/ (deprecated per ADR) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
126 lines
13 KiB
TypeScript
126 lines
13 KiB
TypeScript
import { nativeImage } from 'electron/common'
|
|
import type { BrowserWindow as ElectronBrowserWindow } from 'electron/main'
|
|
import { app } from 'electron/main'
|
|
|
|
import { createRuntimeSnapshot } from './runtime-snapshot'
|
|
import { updateTrayIssueIndicator } from './tray'
|
|
|
|
type RuntimeSnapshot = ReturnType<typeof createRuntimeSnapshot>
|
|
type AppIconTone = 'normal' | 'danger'
|
|
type AppIndicatorImage = ReturnType<typeof nativeImage.createFromBuffer>
|
|
|
|
const HEALTH_ISSUE_AUTH_STATES = new Set(['expired', 'revoked', 'challenge_required'])
|
|
const ISSUE_INDICATOR_REFRESH_MS = 15_000
|
|
const APP_ICON_NORMAL_BASE64 =
|
|
'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'
|
|
const APP_ICON_DANGER_BASE64 =
|
|
'iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAKK0lEQVR4nN1bW1AUVxr+Ts8wXIYZRBku4oWLsouCEh0MEIIIvFuVDVY0pMSKtZukVkTXlMk+rDxsoVl1TclmedkiqQJjNNnasipvrgKFxIS7qLimgA0CgwwDDAMIAzN99gGGNNOX6Z6L7O7/dOY//Z/zfX//5ztnumcIZBgtLlZZp6ZeA6UHCaU5FEgCEAkgiHctpaAA6NKHlbbLL+jjxsn1LbeXfYsAJlmgn6W0maH01uOtW7879PXXTk/ciFTnYHZ2qFarPUmA0wAMUtf6lfBqcpI+iTnNAC7PqFRVOffvzylOgLWo6BAF/gwgXoq4i7wkYDfCPB+XiFwfn7BgQllgEJSe3tPW9o0QdkaADJksKqqgwFdKyLsDXeXztd+HMUHpZgrcbMvMvEAF+K6qAFpRwUzeu3edAIc8EYfA5EIJkeVbGsz7uy13mRFyw9jWdoQArIuDikvolFpdCeA3HokHApxYQuX65CU5bTguTv23kZG7Li4rFbC85m/IIR9gRQ9cBSy1KYDirM7Ov68kYDA7OzRcq30KYLMk+f+1khdP8rBGpUoxtre/YABAGxZWLkV+rQQsgGPG253O3wIAocXFKuvk5AhE9nk/rT3/VoCnuy1vmY2OpKTEq61Wa64o+bUD9zKWWYzhxx+z1aD0II+4Pwl7IreGSWaAg2oK5HAPA//HJc/DDEpz1GTpiw2PyFpUwBokOVkNIMJrIArAqXQ6hL32GkKNRgTFxkK1fj3UhiXpWTSb4ZiYwOLICGZaWmBrbobDZnsZSV5HJoqKqOy7rbQCCIE2Lw/6N95AaEYGiGrVwVPUqNOJ2c5OjN28CWt9vewke7PMyHhhIQ3E2gvZuRPr338foRkZskiL2eyjRxiuqsJ0e3tAlhmxFBZSf5Y8VCpsOHECEW++6RNxdxu9fh2DV66AdTr9qivMqrspUEIefZw20ekQe/my38kDQMzhw9heVQWVTuczTm4/402QkI/odIivrkaY0eh38i6LePVV7KipgUqv9xqne4yyCnDvd/kYBjHnzkGTkBAw8i4LTUrC9gsXAJVKOU4BbmpvBM7dF1VWhrCsLI/gnTYbbI2NmGpsxPzAABbMZgCAOioKwYmJiMjLQ2R+PlR6veQ467KykHDqFPovXvR5+yYj+flUrsAJ+UJ27cKm6mpJwKzdDsv16xirrYVzZkYyyYxWi9ijRxH79ttggoMlx+06ehS2ri6fxNtnDdjwwQeSIB0WC/rfew/Pq6vhmJ72OKZjZgaDn32Gx6WlsD9/Ljl20pkz8ta7BDefNEC7fz9C09NFAS6OjaH32DG86OlRnOTZp0/xqLR0ZZkImT49HRsOHPBJA3yqgHUS2x1rt2PgzBksmM3KqorTto+O4kl5OVi7XXSe+MOH/VcBrEAQKwKO0esRunu3KDDLtWt48eSJIBCoVAg3GmEoLkZ0cTF0RiPAMIJAp3t6MFxXJzrPOqPRp21RzQMnJXocvy4nR/Rs77TZYK6rW3n2zJ1cu2sXtpw9i+D41a8c5oeG8O/KStgePuTtNoM1NYgrLoZaYHcgKhWiXn8dpm+/VSTeLp/XGhCWmSl6V6YaGuBwqb0b+eRLl3jkASBk0yb84tNPEZ6ezsOxODMDy927vBiXrc/N9boCvNYAdUyMeAKamngxUKux5exZMBqNaByj0SDpo48EDzljDQ2icdqkJEXi7ZddICgqShTQXF8fLyZ8927BO881CiBkyxboMjJ48dM9PaJxIbGx/qsAMdHjVYBB/GXxwvg4LyZk2zZJ4pTzWbt9Oy/ePj4uGq8OD1dWAVwegiotcKG7T8rcxwQAyrLC1wr5WHa1EC7Pr2ROMcx+04DFsTFRMEFRUbyYF/39ssgDwExvLy8+aMMG0fkcs7OyKoAV6GdEgzwkZNFiEQUUnJjIi7e1t2N+cHCFuBj5uYEBWDs6eHPqduwQnW/OZFKmAZw2IxrkYaAFiXP6urw8XjzrdKLv/HmwCwuicezCAp5WVi499XGb03DggGjcNFd0PRD2WwXYfvhBFFBkfj4Yd2GiFLbubjwuL8f8s2e8mLmBAXSfOIGp7m7enCqdDtEFBaLzmbnbrsIKULOALNFbEbPl9lRTE6jTKXgaVEdEYGNpKQaqqngCZHvwAB0lJdC/8srK/j3T2wtrZydYh2PpOjfw244fR5DIMwLW4cDzhgYI8QD4SXAfXy3klONbtNkw3dEBvciJMK6kBJY7dzD9+DE/oQ4HJltbMdnSIplkSin06elIeOcdwTkAYLy1FQvL7xDkEHb3iWuAW5CQb/SG+O8pGI0GqVeuQBMTo2hMbjs4NhZ7qqokH4z019YqGlO+BsgQxYn6ekx3dYmC00RHI6OuDuFpaYqFVpeaiqxr1xASHS06/kRXF0x37igiLH8XkHm3nl29KgoQADQGAzJqapB48iRU4eEex1SFhyPl1Clkf/klQuPiJMd+eOGCYsLu/aR73z4qtPZk+ZbbiR9+iLgjRyTBAoDDZsPY3bsw19fD1tMDu8UC0KXvFfrUVEQXFCC6sFBU8LjW+/nn6K6sVCx67j7yYN8+Knjh0miyfIQQpF69isjcXI/A/WHm5mbce/ddUM5bIrmE3XnI1wD3fo6PZVn86+OPBY+7/jZbby++Lyv7+bCkQFd80wAPEy3abOgoKcF4Y2PAyI/eu4fGt95a2fa82Vm83wVkTOSYmcHD8nIMSjzD89Z6v/gCzcePwz415TNObpu0G41USuAU+Th+fXo6tp0+jUgf3xVOPHiAh598Aktrq2KBk6MBpG3vXipL9Dh+2T4AhoICbD5yBOszMxX9QMLS0oK+2loM377tNTk5PtK6d6+dUqpRXAEKM6/W62HYvx+G3Fxok5MRunEj1FotgKXv8y+GhzHd24vRpiaMNDZi0WqVnWRvt28AdtKyZ4+ZAoZAVYBMIP5LvMwKWLZRNQX6KKWGQFdAIJeZ0iS7jAC9DKW02XWh0ANRocdIrID6CvrcwVG62ifQ79HnYz/XKCHNDCHkll8m8gGcaJLlJF5Bkt2NsOwtZiA5+TsKmH3OfADulr/GFLFRk8n0PbP817LLa1kBgRxTwi5WACwDAAtW61VK6bP/NvC+jilhQ7OE/BVY/hfVgZ9+mgfwOwpQv609D+sxkEvCg1FCSNnpoaG5lQQAQF539zdg2fMv4255nWQZiZdhf/z10NA/XB9WnU0TzOaGrdHRv6RAmjt4uBMCAAHCkj4uUJGSFVrDkj4OPhn21cjwcFkDfs4V75+jFCD/TEs7B0r/QAHiDRDZPj8lWYZRQumfTCbT7ys4/xkUTIDLbu/c+SuW0isU2OwVOC5Af1WAQEJl2DNCSDm37GUlAADqExJC5kNDT9AlgYyRQ9gTYH9WgAcbJZReCtJo/nJsSeQFTTIBLqsAmL0pKTmEYQ5SSnMopcmUkEhKqcZrcj5UlYAtAJgE0AdCmlmWvTVqMt2vcCt3IfsPSW3kmWx4VjMAAAAASUVORK5CYII='
|
|
const WINDOWS_OVERLAY_ICON_BASE64 =
|
|
'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'
|
|
|
|
let refreshTimer: ReturnType<typeof setInterval> | null = null
|
|
const appIconCache = new Map<AppIconTone, AppIndicatorImage>()
|
|
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?.()
|
|
}
|