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:
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user