d7b5737806
Addressing issues found reviewing this session's audit commits:
REGRESSIONS FIXED
- updater.ts (L52): reverted the spawn(detached) installer launch. Raw
CreateProcess/spawn fails with ERROR_ELEVATION_REQUIRED if the NSIS build
flips to perMachine (the config comment invites this), and it silently
dropped shell.openPath's launch-error detection. Restored shell.openPath
(ShellExecute honors the elevation manifest) and kept the L52 goal by
replacing the 1500ms timer with setImmediate(app.quit).
- LibraryView.tsx (M15): native <button> doesn't inherit color (UA sets
ButtonText), so the source-card chevron (currentColor) would render wrong
in dark mode. Added color: colorNeutralForeground1 to cardHead.
INEFFECTIVE CHANGE REVERTED
- base.css (W18): forced-color-adjust:auto on * is the CSS default (no-op),
and [class*="backdrop"] never matches Fluent/Griffel's hashed atomic class
names. Reverted; W18 unmarked (needs real High-Contrast testing).
WEAK FIX REVERTED
- Onboarding.tsx (L67): "Skip" and "Get started" called the identical handler
on a single-screen onboarding. Removed the duplicate button; L67 unmarked
(real ask is a "show tips again" revisit affordance).
STYLE / CORRECTNESS
- Stripped UTF-8 BOMs accidentally added to Select/CommandPalette/App/
Onboarding by the PowerShell sanitizer; left pre-existing BOMs untouched.
- DownloadBar.tsx: merged the duplicate @shared/ipc import; removed a stray
double blank line.
- qualityOptions.ts (L29): restored the original `satisfies Record<MediaKind,
readonly string[]>` constraint + the noUncheckedIndexedAccess rationale
comment (the extraction had dropped both for a weaker `as const`).
- downloads.ts: removed double blank line left by the QUALITY_OPTIONS move.
- binaries.ts: corrected YTDLP_MISSING_MSG doc comment ("at startup" -> the
actual call sites).
typecheck + 242 tests + eslint all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
103 lines
4.1 KiB
TypeScript
103 lines
4.1 KiB
TypeScript
import { app, nativeImage, type NativeImage } from 'electron'
|
|
import { join } from 'path'
|
|
import { is } from '@electron-toolkit/utils'
|
|
|
|
/**
|
|
* Resolves the directory holding the bundled binaries (yt-dlp.exe, ffmpeg.exe).
|
|
*
|
|
* In dev they live in the repo at resources/bin/.
|
|
* In a packaged build, electron-builder's extraResources copies them to
|
|
* <resources>/bin (see electron-builder.yml), reachable via process.resourcesPath.
|
|
*/
|
|
export function getBinDir(): string {
|
|
return is.dev ? join(app.getAppPath(), 'resources', 'bin') : join(process.resourcesPath, 'bin')
|
|
}
|
|
|
|
/**
|
|
* The app icon (.ico), for the system tray. In dev it's the repo's build/icon.ico;
|
|
* in a packaged build, electron-builder's extraResources copies it to
|
|
* <resources>/icon.ico (see electron-builder.yml).
|
|
*/
|
|
export function getAppIconPath(): string {
|
|
return is.dev
|
|
? join(app.getAppPath(), 'build', 'icon.ico')
|
|
: join(process.resourcesPath, 'icon.ico')
|
|
}
|
|
|
|
let appIconImage: NativeImage | null = null
|
|
/**
|
|
* The app icon as a cached NativeImage, for OS notifications (W13/L127). Loaded
|
|
* once from getAppIconPath(); a missing icon yields an empty image, which
|
|
* Notification treats as "no icon" (the OS default) rather than erroring. This
|
|
* gives completion/background toasts the real brand glyph — notably on the
|
|
* portable build, which has no installed AUMID shortcut icon for Windows to use.
|
|
*/
|
|
export function getAppIconImage(): NativeImage {
|
|
// Cache only a valid image so a transient read miss isn't latched forever; a
|
|
// genuinely-missing icon just re-reads (cheap — notifications are infrequent).
|
|
if (!appIconImage || appIconImage.isEmpty()) {
|
|
appIconImage = nativeImage.createFromPath(getAppIconPath())
|
|
}
|
|
return appIconImage
|
|
}
|
|
|
|
/**
|
|
* AeroFetch keeps its OWN writable copy of yt-dlp.exe under userData, separate
|
|
* from the read-only bundled seed in resources/bin. The managed copy is what
|
|
* actually gets spawned and self-updated (`--update-to`), so an app reinstall or
|
|
* portable re-extraction — which only ever replace the bundled seed — can never
|
|
* roll a freshly-updated yt-dlp back to a stale version. See ensureManagedYtdlp
|
|
* in ytdlp.ts, which seeds this from getBundledYtdlpPath() on first run.
|
|
*
|
|
* ffmpeg/ffprobe/aria2c stay in getBinDir(): they're not self-updating and
|
|
* yt-dlp finds them via `--ffmpeg-location <binDir>`.
|
|
*/
|
|
function getManagedBinDir(): string {
|
|
return join(app.getPath('userData'), 'bin')
|
|
}
|
|
|
|
/** The bundled, read-only yt-dlp.exe seeded into the managed copy when missing. */
|
|
export function getBundledYtdlpPath(): string {
|
|
return join(getBinDir(), 'yt-dlp.exe')
|
|
}
|
|
|
|
/** The managed (spawned + auto-updated) yt-dlp.exe under userData. */
|
|
export function getYtdlpPath(): string {
|
|
return join(getManagedBinDir(), 'yt-dlp.exe')
|
|
}
|
|
|
|
export function getFfmpegPath(): string {
|
|
return join(getBinDir(), 'ffmpeg.exe')
|
|
}
|
|
|
|
/**
|
|
* yt-dlp finds ffprobe via --ffmpeg-location (the bin dir), so the app never
|
|
* spawns it directly — but it must be present, or duration-aware post-processing
|
|
* (SponsorBlock-remove, --force-keyframes-at-cuts, --split-chapters) fails. This
|
|
* accessor exists so startDownload can assert its presence up front.
|
|
*/
|
|
export function getFfprobePath(): string {
|
|
return join(getBinDir(), 'ffprobe.exe')
|
|
}
|
|
|
|
/** User-facing error shown by download/probe/index/update when yt-dlp.exe is missing. */
|
|
export const YTDLP_MISSING_MSG =
|
|
'yt-dlp.exe is missing. Open Settings → Software update to re-download it.'
|
|
|
|
/** Optional bundled external downloader; absent unless dropped into resources/bin. */
|
|
export function getAria2cPath(): string {
|
|
return join(getBinDir(), 'aria2c.exe')
|
|
}
|
|
|
|
/**
|
|
* Absolute path to a Windows system executable (e.g. taskkill.exe, schtasks.exe).
|
|
*
|
|
* SECURITY (audit F3): system tools are resolved by full path under System32
|
|
* rather than by bare name, so a same-named binary planted in the current
|
|
* working directory or earlier on PATH can't be invoked in their place — a real
|
|
* risk for the portable build, which runs from user-writable locations.
|
|
*/
|
|
export function getSystem32Path(exe: string): string {
|
|
return join(process.env.SystemRoot || 'C:\\Windows', 'System32', exe)
|
|
}
|