Files
laude/src/main/index.ts

125 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
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.
import { app, BrowserWindow, nativeTheme, systemPreferences } from 'electron'
import {
createMainWindow,
createReminderWindow,
showMainWindow
} from './windows'
import { registerIpc } from './ipc'
import { startScheduler, stopScheduler } from './scheduler'
import { createTray } from './tray'
import { flushNow, getState } from './store'
import { wasStartedHidden } from './autostart'
import { broadcastState } from './state-actions'
import { startGamesRegistry, stopGamesRegistry } from './games/registry'
import { initUpdater, stopUpdater } from './updater'
import { IPC } from '@shared/ipc'
import { log } from './logger'
import { installSecurityHardening } from './security'
const APP_ID = 'com.anril.exercise-reminder'
// Глобальная сеть безопасности: без этих обработчиков необработанное
// исключение/rejection в main-процессе валит приложение молча — пользователь
// видит, что окно просто исчезло, а в логах пусто. Логируем всё в latest.log.
// uncaughtException дополнительно флашит state, чтобы не потерять данные.
process.on('uncaughtException', (err) => {
log.error('[fatal] uncaughtException', err)
try {
flushNow()
} catch {
// flush сам может бросить (диск/AV) — мы уже в аварийном пути, глушим.
}
})
process.on('unhandledRejection', (reason) => {
log.error('[fatal] unhandledRejection', reason)
})
// Must be set BEFORE app.whenReady() for Windows toasts to show
// the correct app name / icon in Action Center.
app.setAppUserModelId(APP_ID)
app.setName('Exercise Reminder')
const gotLock = app.requestSingleInstanceLock()
if (!gotLock) {
app.quit()
} else {
app.on('second-instance', () => showMainWindow())
app.whenReady().then(() => {
installSecurityHardening()
registerIpc()
createTray()
const hidden = wasStartedHidden() || getState().settings.startMinimized
createMainWindow(!hidden)
// Pre-create the reminder window so first-trigger is instant (no load lag).
createReminderWindow()
startScheduler()
startGamesRegistry().catch((err) =>
log.error('[index] games registry failed', err)
)
initUpdater()
nativeTheme.on('updated', () => {
const theme = nativeTheme.shouldUseDarkColors ? 'dark' : 'light'
for (const win of BrowserWindow.getAllWindows()) {
if (!win.isDestroyed()) win.webContents.send(IPC.evtThemeChanged, theme)
}
})
try {
systemPreferences.on('accent-color-changed' as never, () => {
try {
const color = '#' + systemPreferences.getAccentColor()
for (const win of BrowserWindow.getAllWindows()) {
if (!win.isDestroyed())
win.webContents.send(IPC.evtAccentChanged, color)
}
} catch {
// ignore
}
})
} catch {
// older Electron / non-Windows
}
})
app.on('window-all-closed', () => {
// Keep running in tray instead of quitting when all windows closed.
if (!getState().settings.minimizeToTray) {
app.quit()
}
})
// Перехватываем первый before-quit, чтобы дождаться `stopGamesRegistry`
// (закрывает GSI HTTP server со всеми pending connections). Без этого
// следующий запуск получает EADDRINUSE на port 4701 (TIME_WAIT), и
// GSI молча не работает. После cleanup'а — реально quit.
let quitting = false
app.on('before-quit', (e) => {
if (quitting) return
e.preventDefault()
quitting = true
stopScheduler()
stopUpdater()
void (async () => {
try {
await stopGamesRegistry()
} catch (err) {
log.error('[index] stopGamesRegistry threw', err)
}
flushNow()
app.exit(0)
})()
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createMainWindow(true)
else showMainWindow()
})
// Broadcast state once on ready so any prebuilt windows hydrate.
app.whenReady().then(() => broadcastState())
}