Harden audit findings: correctness, type-safety, Windows conventions & polish
Audit-pass over CODE-AUDIT.md (~48 items closed this pass; all verified — typecheck + 234 tests + eslint + prettier green). Correctness / bugs: - B3: match the release checksum to the asset's filename line (no wrong-hash verify) - B4: newline-safe metadata probe (one --print with a unit-separator delimiter) - B5 / L88: guard the meta event against canceled items; progress no longer promotes a queued item outside pump() - B7: cookie-login promise always resolves (handles destroy-without-close) - L146: trim parser rejects >2 colon-group times; M36: Library selection counts only actionable rows - L11 / L50 / L156 / L57 / L159 / L15 / L3: live queue count, empty-cookie message, schedule picker min, dead-code/comment cleanup Type safety: - Enable noUncheckedIndexedAccess + noFallthroughCasesInSwitch (15 real edge cases fixed) Resilience / Windows / metadata: - R5: settings write failure handled (no unhandled IPC rejection; reconciles to truth) - W1 / W5 / W6: min window size, seeded folder picker, parented sign-in window; L147 dead macOS branches removed - CL1: shared stdout markers; package/builder metadata (license, homepage, repository, copyright, tsbuildinfo glob) Copy / docs / tests: - M37 / SR9 dev-jargon cleanup in hints; M8 / M25 / M26 / L66 / L80 / L81 reconciled - New unit tests for L35 (isValidMediaItem) and L36 (compareVersions) This commit also checkpoints the previously-uncommitted feat/tray-background-clipboard work it builds on: background running + auto-download, library clipboard detection, tray, binary management & library scale, credential encryption at rest, the shared jsonStore and ui/ primitives, and the eslint/prettier tooling. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+17
-37
@@ -1,59 +1,39 @@
|
||||
import { app } from 'electron'
|
||||
import { join } from 'path'
|
||||
import { readFileSync, writeFileSync, existsSync } from 'fs'
|
||||
import type { HistoryEntry } from '@shared/ipc'
|
||||
import { isValidHistoryEntry } from './validation'
|
||||
import { createJsonStore } from './jsonStore'
|
||||
|
||||
// Plain JSON in userData (portable build redirects userData next to the exe).
|
||||
// Kept simple per the build plan; can migrate to better-sqlite3 later.
|
||||
// Kept simple per the build plan; can migrate to better-sqlite3 later. Atomic
|
||||
// writes, corruption backup, and caching come from the shared jsonStore (R1–R3).
|
||||
const MAX_ENTRIES = 500
|
||||
|
||||
function historyFile(): string {
|
||||
return join(app.getPath('userData'), 'history.json')
|
||||
}
|
||||
// Per-entry validation (isValidHistoryEntry) so a hand-edited or corrupted
|
||||
// history.json can't feed the UI (or openPath) entries with the wrong shape —
|
||||
// invalid rows are dropped rather than trusted. (audit S5)
|
||||
const store = createJsonStore('history.json', isValidHistoryEntry, MAX_ENTRIES)
|
||||
|
||||
// Per-entry validation (isValidHistoryEntry, in validation.ts) so a hand-edited
|
||||
// or corrupted history.json can't feed the UI (or openPath) entries with the
|
||||
// wrong shape — invalid rows are dropped rather than trusted. (audit S5)
|
||||
export function listHistory(): HistoryEntry[] {
|
||||
try {
|
||||
if (!existsSync(historyFile())) return []
|
||||
const data = JSON.parse(readFileSync(historyFile(), 'utf8'))
|
||||
return Array.isArray(data) ? data.filter(isValidHistoryEntry) : []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function save(entries: HistoryEntry[]): void {
|
||||
try {
|
||||
writeFileSync(historyFile(), JSON.stringify(entries.slice(0, MAX_ENTRIES), null, 2))
|
||||
} catch {
|
||||
/* best-effort; a read-only data dir just means no persisted history */
|
||||
}
|
||||
return store.read()
|
||||
}
|
||||
|
||||
export function addHistory(entry: HistoryEntry): HistoryEntry[] {
|
||||
// De-dupe by id (a retry of the same item replaces its prior entry).
|
||||
const entries = [entry, ...listHistory().filter((e) => e.id !== entry.id)]
|
||||
save(entries)
|
||||
return entries
|
||||
// De-dupe by id AND url (M35): a History re-download re-queues via addFromUrl,
|
||||
// which mints a NEW id, so id-only de-dup would let the same video accumulate a
|
||||
// fresh row on every re-download. Dropping any prior entry with the same url
|
||||
// keeps one row per video, refreshed to the top.
|
||||
const prior = listHistory().filter((e) => e.id !== entry.id && e.url !== entry.url)
|
||||
return store.write([entry, ...prior])
|
||||
}
|
||||
|
||||
export function removeHistory(id: string): HistoryEntry[] {
|
||||
const entries = listHistory().filter((e) => e.id !== id)
|
||||
save(entries)
|
||||
return entries
|
||||
return store.write(listHistory().filter((e) => e.id !== id))
|
||||
}
|
||||
|
||||
export function removeManyHistory(ids: string[]): HistoryEntry[] {
|
||||
const remove = new Set(ids)
|
||||
const entries = listHistory().filter((e) => !remove.has(e.id))
|
||||
save(entries)
|
||||
return entries
|
||||
return store.write(listHistory().filter((e) => !remove.has(e.id)))
|
||||
}
|
||||
|
||||
export function clearHistory(): HistoryEntry[] {
|
||||
save([])
|
||||
return []
|
||||
return store.write([])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user