Files
AeroFetch/src/main/reveal.ts
T
debont80 bb5dd6c438 Add Seal-parity download options + playlist support (Phase A)
Phase A of the Seal feature-parity roadmap: configurable post-processing via a
shared DownloadOptions model (src/shared/ipc.ts), editable as persisted defaults
in Settings and overridable per-download in the download bar.

- audio formats (mp3/m4a/opus/flac/wav/aac), video container (mp4/mkv/webm),
  preferred-codec sort (-S vcodec)
- subtitles (download/embed/langs/auto), SponsorBlock (remove/mark + categories
  + force-keyframes-at-cuts), embed chapters/metadata/thumbnail, square-crop
  audio artwork
- playlist downloads: probe via `-J --flat-playlist`, inline selection UI
  (checkbox list, select-all/none, live count); each entry enqueued as its own
  single-video download, reusing the existing queue/concurrency/history
- reusable DownloadOptionsForm shared by Settings and the download bar so the
  defaults and per-download override never drift
- ROADMAP.md tracking full Seal parity (phases A-E)

Also includes in-tree security hardening that landed alongside: preload sandbox
(CJS preload output), http(s)-only window-open + blocked renderer navigation,
argv-injection guard for URLs (src/main/url.ts), extension-allowlisted file
open/reveal (src/main/reveal.ts), yt-dlp version-check timeout, and a minimal
window.electron presence marker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 19:37:22 -04:00

43 lines
1.5 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)
}