Files
AeroFetch/src/main/reveal.ts
T
debont80 1376c2dee8 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>
2026-06-30 13:02:54 -04:00

61 lines
1.6 KiB
TypeScript

import { shell } from 'electron'
import { existsSync, statSync } from 'fs'
import { extname, isAbsolute } from 'path'
/**
* Open/reveal helpers used by the shell:* IPC handlers.
*
* The renderer supplies the path (it originates from yt-dlp's after-move print),
* but the IPC boundary must not trust it blindly: a compromised renderer could
* otherwise call openPath() on an arbitrary executable and have the OS run it.
* So openPath is confined to existing files with a known media extension —
* never .exe/.bat/.ps1/etc.
*/
const OPENABLE_EXTENSIONS = new Set([
// video
'.mp4',
'.mkv',
'.webm',
'.mov',
'.avi',
'.flv',
'.ts',
'.m4v',
'.3gp',
'.ogv',
// audio
'.mp3',
'.m4a',
'.opus',
'.ogg',
'.oga',
'.aac',
'.flac',
'.wav',
'.wma',
// subtitle sidecars (plain text — safe to open)
'.vtt',
'.srt'
])
/** Open a downloaded media file with its default app. Returns '' on success,
* or an error string (matching shell.openPath's contract). */
export async function safeOpenPath(p: unknown): Promise<string> {
if (typeof p !== 'string' || !isAbsolute(p)) return 'Invalid path.'
if (!OPENABLE_EXTENSIONS.has(extname(p).toLowerCase())) {
return 'Refusing to open this file type.'
}
try {
if (!statSync(p).isFile()) return 'Not a file.'
} catch {
return 'File not found.'
}
return shell.openPath(p)
}
/** Reveal a path in the OS file manager. No-op for missing/invalid paths. */
export function safeShowInFolder(p: unknown): void {
if (typeof p !== 'string' || !isAbsolute(p) || !existsSync(p)) return
shell.showItemInFolder(p)
}