fix(main): L136 — enforce incognito in main (no errorlog / toast / cookie leak)
Incognito lived only in the renderer (history skip), so a "private" download still leaked in main: errorlog entries (title+URL), OS toasts showing the title, and the signed-in cookies attached — the UI promised "no logging, no history, no cookies" but only history held. Plumb `incognito` through StartDownloadOptions and enforce it in main: - logFailure skips the errorlog write (and the pre-spawn errorlog in ipc.ts). - notify uses a generic outcome title and drops the detail body, so no title/URL lands in the Windows Action Center. - both cookie sources (--cookies login jar, --cookies-from-browser) are withheld, so a private download can't be tied to the user's identity. typecheck + 262 tests + eslint + prettier green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+38
-8
@@ -90,9 +90,12 @@ function send(wc: WebContents, ev: DownloadEvent): void {
|
||||
}
|
||||
|
||||
/** Native OS notification on completion/failure, gated by Settings.notifyOnComplete. */
|
||||
function notify(wc: WebContents, title: string, body: string): void {
|
||||
function notify(wc: WebContents, title: string, body: string, incognito = false): void {
|
||||
if (!getSettings().notifyOnComplete || !Notification.isSupported()) return
|
||||
const n = new Notification({ title, body, icon: getAppIconImage() })
|
||||
// L136: an incognito ("private") download must not leak its title/URL into an OS
|
||||
// toast (it persists in the Windows Action Center). Callers pass a generic outcome
|
||||
// title for the private case; here we additionally drop the detail body.
|
||||
const n = new Notification({ title, body: incognito ? '' : body, icon: getAppIconImage() })
|
||||
n.on('click', () => {
|
||||
if (wc.isDestroyed()) return
|
||||
const win = BrowserWindow.fromWebContents(wc)
|
||||
@@ -106,6 +109,9 @@ function notify(wc: WebContents, title: string, body: string): void {
|
||||
}
|
||||
|
||||
function logFailure(opts: StartDownloadOptions, title: string | undefined, error: string): void {
|
||||
// L136: incognito downloads are never recorded — not even a failure entry, which
|
||||
// would persist the title + URL in the user-facing diagnostics log.
|
||||
if (opts.incognito) return
|
||||
addErrorLog({
|
||||
id: opts.id,
|
||||
title,
|
||||
@@ -199,7 +205,9 @@ export function buildCommand(opts: StartDownloadOptions, cookiesFile?: string):
|
||||
proxy: settings.proxy,
|
||||
rateLimit: settings.rateLimit,
|
||||
aria2cPath,
|
||||
cookiesFromBrowser: settings.cookieSource === 'browser' ? settings.cookiesBrowser : undefined,
|
||||
// L136/M6: incognito attaches no cookies from the browser either.
|
||||
cookiesFromBrowser:
|
||||
!opts.incognito && settings.cookieSource === 'browser' ? settings.cookiesBrowser : undefined,
|
||||
cookiesFile,
|
||||
restrictFilenames: settings.restrictFilenames,
|
||||
downloadArchivePath: settings.downloadArchive ? getDownloadArchivePath() : undefined,
|
||||
@@ -279,7 +287,9 @@ export function startDownload(wc: WebContents, opts: StartDownloadOptions): Star
|
||||
// yt-dlp reads --cookies once at startup; we delete it the moment the download
|
||||
// settles, so the plaintext never lingers at rest.
|
||||
let cookiesFile: string | undefined
|
||||
if (getSettings().cookieSource === 'login' && hasStoredCookies()) {
|
||||
// L136/M6: an incognito download attaches no saved login cookies (the UI promises
|
||||
// "no cookies"), so it can't be tied to the user's signed-in identity.
|
||||
if (!opts.incognito && getSettings().cookieSource === 'login' && hasStoredCookies()) {
|
||||
// Unique per spawn (not just per id): a retry/resume reuses opts.id, and the
|
||||
// superseded download's cleanupCookies() must not unlink the new spawn's jar.
|
||||
const tmp = join(tmpdir(), `aerofetch-cookies-${opts.id}-${++spawnSeq}.txt`)
|
||||
@@ -390,7 +400,12 @@ function wireChildProcess(params: {
|
||||
const msg = `Download stalled — no activity for ${Math.round(STALL_TIMEOUT_MS / 60_000)} min. Stopped; retry to resume.`
|
||||
send(wc, { type: 'error', id: opts.id, error: msg })
|
||||
logFailure(opts, getTitle(), msg)
|
||||
notify(wc, getTitle() ?? 'Download failed', msg)
|
||||
notify(
|
||||
wc,
|
||||
opts.incognito ? 'Download stopped' : (getTitle() ?? 'Download failed'),
|
||||
msg,
|
||||
opts.incognito
|
||||
)
|
||||
}, STALL_TIMEOUT_MS)
|
||||
}
|
||||
bumpWatchdog()
|
||||
@@ -434,7 +449,12 @@ function wireChildProcess(params: {
|
||||
if (!rec.canceled && !rec.paused) {
|
||||
send(wc, { type: 'error', id: opts.id, error: err.message })
|
||||
logFailure(opts, getTitle(), err.message)
|
||||
notify(wc, getTitle() ?? 'Download failed', err.message)
|
||||
notify(
|
||||
wc,
|
||||
opts.incognito ? 'Download failed' : (getTitle() ?? 'Download failed'),
|
||||
err.message,
|
||||
opts.incognito
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -449,12 +469,22 @@ function wireChildProcess(params: {
|
||||
if (rec.canceled || rec.paused) return
|
||||
if (code === 0) {
|
||||
send(wc, { type: 'done', id: opts.id, filePath })
|
||||
notify(wc, getTitle() ?? 'Download complete', 'Finished downloading.')
|
||||
notify(
|
||||
wc,
|
||||
opts.incognito ? 'Download complete' : (getTitle() ?? 'Download complete'),
|
||||
'Finished downloading.',
|
||||
opts.incognito
|
||||
)
|
||||
} else {
|
||||
const msg = cleanError(stderrTail) || `yt-dlp exited with code ${code}`
|
||||
send(wc, { type: 'error', id: opts.id, error: msg })
|
||||
logFailure(opts, getTitle(), msg)
|
||||
notify(wc, getTitle() ?? 'Download failed', msg)
|
||||
notify(
|
||||
wc,
|
||||
opts.incognito ? 'Download failed' : (getTitle() ?? 'Download failed'),
|
||||
msg,
|
||||
opts.incognito
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user