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

@@ -21,6 +21,7 @@ import {
SAMPLE_EXERCISES,
Settings
} from '@shared/types'
import { log } from './logger'
/**
* Keep at most this many history entries (≈2.7 years at 10/day).
@@ -89,12 +90,11 @@ function quarantineCorrupt(p: string, reason: string): void {
.replace(/Z$/, '')
const dest = `${p}.corrupt-${stamp}`
renameSync(p, dest)
console.error(
`[store] app-state.json was unreadable (${reason}); ` +
`moved to ${dest} and starting fresh.`
log.error(
`[store] app-state.json was unreadable (${reason}); moved to ${dest} and starting fresh.`
)
} catch (e) {
console.error('[store] failed to quarantine corrupt state file:', e)
log.error('[store] failed to quarantine corrupt state file', e)
}
}
@@ -182,7 +182,7 @@ function load(): PersistedState {
try {
raw = readFileSync(p, 'utf-8')
} catch (e) {
console.error('[store] cannot read state file:', e)
log.error('[store] cannot read state file', e)
return makeInitial() // do not quarantine — we can't read it anyway
}
let parsed: unknown
@@ -266,7 +266,7 @@ async function atomicWrite(path: string, contents: string): Promise<void> {
await new Promise<void>((r) => setTimeout(r, delay))
}
}
console.error('[store] atomic write failed after retries:', lastErr)
log.error('[store] atomic write failed after retries', lastErr)
}
/**
@@ -298,7 +298,7 @@ function atomicWriteSync(path: string, contents: string): void {
}
}
}
console.error('[store] atomic sync write failed after retries:', lastErr)
log.error('[store] atomic sync write failed after retries', lastErr)
}
async function flush(): Promise<void> {