chore: sprint D — sandbox, self-hosted fonts, logger с ротацией

#6  sandbox: true на обоих BrowserWindow (раньше false). Preload
    использует только contextBridge + ipcRenderer (оба sandbox-safe),
    никаких Node-built-ins. OS-уровневый sandbox изолирует renderer
    от GPU/IPC процессов; даже RCE в зависимости renderer'а не
    получит Node-доступа через preload.

#17 self-host шрифтов через @fontsource/* пакеты. Раньше тянулись
    с fonts.googleapis.com — внешняя CSP-зависимость + отсутствие
    интернета = шрифты не загружались. Теперь .woff/.woff2 в bundle
    (22 файла × 15-30KB = ~500KB).
    Подкрутили CSP: убрали https://fonts.* origins, добавили
    connect-src 'self', base-uri 'self', frame-ancestors 'none'.

#22 src/main/logger.ts — структурный лог с уровнями
    (debug/info/warn/error) и ротацией. Пишет в
    %APPDATA%/Exercise Reminder/logs/latest.log (≤1MB) и
    дублирует в console. При 1MB latest.log → prev.log
    (предыдущий prev.log удаляется). LAUDE_DEBUG=1 включает
    debug-уровень.

    Подключён в hot paths: store (corrupt/atomic write fails),
    updater (silent check errors), gsi-server (bad requests,
    handler throws), games/registry (GSI start, reconcile, match_end
    summary), games/dota2 (rejected token, POST_GAME detection).

    Особенно полезно для диагностики «челленджи не срабатывают»:
    лог покажет (а) пришёл ли вообще GSI payload (token verify),
    (б) детектировался ли POST_GAME, (в) сколько challenges были
    enabled и которые из них дали 0 reps.

    Logger — единственный файл с `eslint-disable no-console` (он
    намеренно дублирует в stderr).
This commit is contained in:
AnRil
2026-05-22 01:24:30 +07:00
parent e7ccca98e7
commit 34fb03b265
11 changed files with 254 additions and 27 deletions

View File

@@ -18,6 +18,7 @@ import {
isSteamRunning
} from './steam-launch-options'
import type { GameId, GameStatus, LaunchOptionStatus } from '@shared/types'
import { log } from '../logger'
const APP_ID = '570'
const INSTALL_DIR = 'dota 2 beta'
@@ -198,6 +199,8 @@ export class Dota2Provider implements GameProvider {
this.latest = undefined
}
private rejectedTokenLogged = false
private handle(g: DotaGsi): void {
// Verify the per-install token. Dota always sends auth.token; anything
// without it (or with the wrong one) is some other process on localhost
@@ -207,6 +210,15 @@ export class Dota2Provider implements GameProvider {
typeof incoming !== 'string' ||
!safeEqualStrings(incoming, this.token)
) {
// Логируем только ОДИН раз за процесс — Dota шлёт payload каждые
// ~100ms во время матча, иначе zass'мём latest.log.
if (!this.rejectedTokenLogged) {
this.rejectedTokenLogged = true
log.warn(
'[dota2] GSI payload with invalid/missing token rejected. ' +
'Если приложение переустанавливалось — заново подключи Dota 2 в Games.'
)
}
return
}
@@ -235,8 +247,12 @@ export class Dota2Provider implements GameProvider {
if (prev && prev !== state && state === 'DOTA_GAMERULES_STATE_POST_GAME') {
// De-dupe: Dota can fire POST_GAME repeatedly while the scoreboard is open.
const now = Date.now()
if (now - this.lastMatchEndAt < 30_000) return
if (now - this.lastMatchEndAt < 30_000) {
log.debug('[dota2] suppressed duplicate POST_GAME within 30s window')
return
}
this.lastMatchEndAt = now
log.info('[dota2] POST_GAME detected, emitting match_end event')
const p = this.latest?.player ?? {}
const m = this.latest?.map ?? {}