This commit is contained in:
2026-10-17 21:21:03 +07:00
parent 1d4028b183
commit 402e212332
15 changed files with 1017 additions and 24 deletions
+67 -8
View File
@@ -13,6 +13,7 @@ import { createReadStream } from 'node:fs'
import { stat } from 'node:fs/promises'
import { join } from 'node:path'
import { Readable } from 'node:stream'
import { DiagnosticLog } from './diagnostics.js'
import { Controller, parseCommand } from './controller.js'
import { WindowSettings, fitControlBounds, preferredDisplayId } from './window-settings.js'
import { parseRange } from './range.js'
@@ -43,8 +44,18 @@ let settings: WindowSettings
let sleepBlocker: number | null = null
let hostNotice = ''
let suspended = false
let diagnostics: DiagnosticLog | null = null
const lastErrors = { main: false, music: false, scene: false }
let saveTimer: ReturnType<typeof setTimeout> | undefined
const controller = new Controller((state) => {
for (const channel of ['main', 'music', 'scene'] as const) {
const failed =
channel === 'scene'
? state.scene.status === 'error'
: state.channels[channel].status === 'error'
if (failed && !lastErrors[channel]) diagnostics?.record('media-error', { channel })
lastErrors[channel] = failed
}
for (const window of [control, output])
if (window && !window.isDestroyed()) window.webContents.send('state', state)
})
@@ -101,6 +112,23 @@ function closeOutput(reason = '', destroy = true) {
if (reason) outputEpoch++
const window = output
if (!window && (!reason || controller.state.timer.status !== 'running')) return
diagnostics?.record('output-close', {
reason: !reason
? 'operator'
: reason.includes('сон')
? 'power'
: reason.includes('полноэкран')
? 'fullscreen'
: reason.includes('Процесс')
? 'renderer'
: reason.includes('не отвечает')
? 'unresponsive'
: reason.includes('загрузить')
? 'document'
: reason.includes('Параметры')
? 'geometry'
: 'display',
})
output = null
if (window) closingWindow = window
selectedDisplay = null
@@ -123,6 +151,7 @@ function saveWindowSettings() {
try {
settings.save()
} catch {
diagnostics?.record('settings-error')
hostNotice = 'Не удалось сохранить расположение окон; материалы игры не затронуты'
}
}
@@ -180,6 +209,7 @@ async function show(displayId: number) {
saveWindowSettings()
secure(window)
controller.output(true)
diagnostics?.record('output-open')
try {
sleepBlocker = powerSaveBlocker.start('prevent-display-sleep')
} catch {
@@ -241,10 +271,18 @@ app.on('second-instance', () => {
app
.whenReady()
.then(async () => {
diagnostics = new DiagnosticLog(join(app.getPath('userData'), 'logs'))
diagnostics.record('app-start', {
version: app.getVersion(),
electron: process.versions.electron,
platform: process.platform,
arch: process.arch,
})
settings = new WindowSettings(join(app.getPath('userData'), 'windows.json'))
try {
await settings.load()
} catch (error) {
diagnostics.record('settings-error')
hostNotice = error instanceof Error ? error.message : 'Не удалось восстановить окна'
}
const packages = new PackageStore(join(app.getPath('userData'), 'packages'))
@@ -258,8 +296,14 @@ app
decrypt: (value) => safeStorage.decryptString(value),
})
await online.initialize()
let previousConnection = online.state.connection
const logConnection = () => {
const status = online!.state.connection
if (status !== previousConnection) diagnostics?.record('connection', { status })
previousConnection = status
}
const onlineTick = setInterval(() => {
void online?.poll()
void online?.poll().then(logConnection)
}, 1000)
const timerTick = setInterval(() => controller.tick(), 250)
app.on('before-quit', () => {
@@ -271,6 +315,7 @@ app
clearInterval(onlineTick)
online?.close()
packages.cancel()
diagnostics?.record('app-stop')
})
const handleAppRequest: Parameters<typeof protocol.handle>[1] = async (request) => {
const url = new URL(request.url)
@@ -378,7 +423,20 @@ app
ipcMain.handle('online-action', async (event, intent: unknown) => {
requireControl(event)
if (selectingPackage) throw new Error('Подождите проверки пакета')
return online!.act(intent)
const action =
intent && typeof intent === 'object' && 'action' in intent ? intent.action : null
try {
const result = await online!.act(intent)
if (['download', 'check', 'remove'].includes(String(action)))
diagnostics?.record('package', { action, status: 'success' })
return result
} catch (error) {
if (['download', 'check', 'remove'].includes(String(action)))
diagnostics?.record('package', { action, status: 'failed' })
throw error
} finally {
logConnection()
}
})
ipcMain.handle('close-output', (event) => {
requireControl(event)
@@ -484,8 +542,8 @@ app
return
try {
controller.reportScene(report)
} catch (error) {
console.error('Ошибка подтверждения сцены', error)
} catch {
diagnostics?.record('invalid-report')
}
})
ipcMain.on('report', (event, report: unknown) => {
@@ -496,8 +554,8 @@ app
return
try {
controller.report(report)
} catch (error) {
console.error('Ошибка отчёта проигрывателя', error)
} catch {
diagnostics?.record('invalid-report')
}
})
screen.on('display-removed', (_event, display) => {
@@ -526,8 +584,9 @@ app
})
await control.loadURL('screen-app://app/index.html')
})
.catch((error) => {
console.error(error)
.catch(() => {
diagnostics?.record('startup-error')
console.error('SCREEN_STARTUP_FAILED')
app.quit()
})
app.on('window-all-closed', () => app.quit())