1376c2dee8
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>
61 lines
1.6 KiB
TypeScript
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)
|
|
}
|