Format code with Prettier (8 modified files from H1 decomposition)
Aligns with CC2 enforcement: ESLint + Prettier + noImplicitAny now enforced. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
+15
-283
@@ -1,77 +1,26 @@
|
||||
import {
|
||||
app,
|
||||
shell,
|
||||
BrowserWindow,
|
||||
ipcMain,
|
||||
dialog,
|
||||
clipboard,
|
||||
nativeTheme,
|
||||
Notification,
|
||||
Menu
|
||||
} from 'electron'
|
||||
import { app, shell, BrowserWindow, nativeTheme, Notification, Menu } from 'electron'
|
||||
import { join, resolve } from 'path'
|
||||
import { existsSync } from 'fs'
|
||||
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
||||
import {
|
||||
IpcChannels,
|
||||
type StartDownloadOptions,
|
||||
type Settings,
|
||||
type HistoryEntry,
|
||||
type CommandTemplate,
|
||||
type YtdlpUpdateChannel,
|
||||
type SystemThemeInfo,
|
||||
type TaskbarProgress
|
||||
} from '@shared/ipc'
|
||||
import { IpcChannels } from '@shared/ipc'
|
||||
import { PAGE_BACKGROUND } from '@shared/theme'
|
||||
import { getYtdlpVersion, updateYtdlp, runStartupYtdlpAutoUpdate } from './ytdlp'
|
||||
import { getFfmpegVersions } from './ffmpeg'
|
||||
import { checkForAppUpdate, downloadAppUpdate, runAppUpdate } from './updater'
|
||||
import { probeMedia } from './probe'
|
||||
import {
|
||||
registerIpcHandlers,
|
||||
resolveBackgroundMode,
|
||||
applyNativeTheme,
|
||||
getSystemThemeInfo
|
||||
} from './ipc'
|
||||
import { runStartupYtdlpAutoUpdate } from './ytdlp'
|
||||
import { getAppIconImage } from './binaries'
|
||||
import {
|
||||
startDownload,
|
||||
cancelDownload,
|
||||
pauseDownload,
|
||||
previewCommand,
|
||||
hasActiveDownloads
|
||||
} from './download'
|
||||
import { runTerminal, cancelTerminal } from './terminal'
|
||||
import {
|
||||
getSettings,
|
||||
setSettings,
|
||||
ensureMediaDirs,
|
||||
applyLaunchAtStartup,
|
||||
migrateSecretsAtRest
|
||||
} from './settings'
|
||||
import { listHistory, addHistory, removeHistory, removeManyHistory, clearHistory } from './history'
|
||||
import { listTemplates, saveTemplate, removeTemplate } from './templates'
|
||||
import { hasActiveDownloads } from './download'
|
||||
import { getSettings, applyLaunchAtStartup, migrateSecretsAtRest } from './settings'
|
||||
import { ensureMediaDirs } from './paths'
|
||||
import { setupPortableData } from './portable'
|
||||
import { safeOpenPath, safeShowInFolder } from './reveal'
|
||||
import {
|
||||
openCookieLoginWindow,
|
||||
getCookiesStatus,
|
||||
clearCookies,
|
||||
migrateLegacyCookies
|
||||
} from './cookies'
|
||||
import { migrateLegacyCookies } from './cookies'
|
||||
import { attachEditContextMenu } from './contextMenu'
|
||||
import { listErrorLog, addErrorLog, clearErrorLog } from './errorlog'
|
||||
import { flushAllStores } from './jsonStore'
|
||||
import { exportBackup, importBackup } from './backup'
|
||||
import { extractIncomingUrl, registerSendToShortcut, focusWindow } from './deeplink'
|
||||
import {
|
||||
listSources,
|
||||
getSource,
|
||||
removeSource,
|
||||
listMediaItems,
|
||||
setMediaItemDownloaded,
|
||||
setSourceWatched
|
||||
} from './sources'
|
||||
import { indexSource } from './indexer'
|
||||
import { syncWatchedSources } from './sync'
|
||||
import { getScheduledSync, setScheduledSync, isSyncLaunch } from './schedule'
|
||||
import { isSyncLaunch } from './schedule'
|
||||
import { createTray, markQuitting, isQuitting } from './tray'
|
||||
import { getActiveBadge, getErrorBadge } from './badge'
|
||||
import { openPoTokenWindow } from './poToken'
|
||||
|
||||
// Only one instance ever runs. A second launch — e.g. the OS invoking us again
|
||||
// for an aerofetch:// link or a "Send to AeroFetch" file — hands its argv to
|
||||
@@ -110,25 +59,6 @@ if (is.dev && devScript) {
|
||||
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
|
||||
// Resolve 'system' against the OS preference so callers always get a concrete color.
|
||||
function resolveBackgroundMode(theme: Settings['theme']): 'light' | 'dark' {
|
||||
return theme === 'system' ? (nativeTheme.shouldUseDarkColors ? 'dark' : 'light') : theme
|
||||
}
|
||||
|
||||
// Keep the OS-drawn title bar consistent with the in-app theme (W3). Setting
|
||||
// themeSource to 'light'/'dark' forces the caption/chrome to match; 'system'
|
||||
// restores OS control. Called at startup and on every theme change.
|
||||
function applyNativeTheme(theme: Settings['theme']): void {
|
||||
nativeTheme.themeSource = theme
|
||||
}
|
||||
|
||||
function getSystemThemeInfo(): SystemThemeInfo {
|
||||
return {
|
||||
shouldUseDarkColors: nativeTheme.shouldUseDarkColors,
|
||||
shouldUseHighContrastColors: nativeTheme.shouldUseHighContrastColors
|
||||
}
|
||||
}
|
||||
|
||||
// Tell the user (once per run) that closing the window left AeroFetch running so
|
||||
// an in-progress download could finish — shown only when they haven't already
|
||||
// opted into tray mode, so a window that "won't close" doesn't read as a bug.
|
||||
@@ -263,204 +193,6 @@ function createWindow(): void {
|
||||
}
|
||||
}
|
||||
|
||||
function registerIpcHandlers(): void {
|
||||
// M30: a broken contextBridge causes the renderer to silently run in preview/mock
|
||||
// mode. Catch it here and show a hard error so the failure is never invisible.
|
||||
ipcMain.once(IpcChannels.preloadBridgeFailure, (_e, detail: unknown) => {
|
||||
dialog.showErrorBox(
|
||||
'AeroFetch could not start',
|
||||
`The renderer bridge failed to initialize. Please reinstall the application.\n\n${String(detail)}`
|
||||
)
|
||||
app.quit()
|
||||
})
|
||||
|
||||
ipcMain.handle(IpcChannels.appVersion, () => app.getVersion())
|
||||
|
||||
ipcMain.handle(IpcChannels.appUpdateCheck, () => checkForAppUpdate())
|
||||
ipcMain.handle(IpcChannels.appUpdateDownload, (e, url: string) =>
|
||||
downloadAppUpdate(url, e.sender)
|
||||
)
|
||||
ipcMain.handle(IpcChannels.appUpdateRun, (_e, filePath: string) => runAppUpdate(filePath))
|
||||
|
||||
ipcMain.handle(IpcChannels.ytdlpVersion, () => getYtdlpVersion())
|
||||
|
||||
ipcMain.handle(IpcChannels.ffmpegVersion, () => getFfmpegVersions())
|
||||
|
||||
ipcMain.handle(IpcChannels.probe, (_e, url: string) => probeMedia(url))
|
||||
|
||||
ipcMain.handle(IpcChannels.downloadStart, (e, opts: StartDownloadOptions) => {
|
||||
const result = startDownload(e.sender, opts)
|
||||
// Pre-spawn failures (missing yt-dlp.exe, bad URL, duplicate id) never reach
|
||||
// download.ts's own close/error handlers, so log them here instead.
|
||||
if (!result.ok) {
|
||||
addErrorLog({
|
||||
id: opts.id,
|
||||
url: opts.url,
|
||||
kind: opts.kind,
|
||||
error: result.error ?? 'Unknown error',
|
||||
occurredAt: Date.now()
|
||||
})
|
||||
}
|
||||
return result
|
||||
})
|
||||
ipcMain.handle(IpcChannels.downloadCancel, (_e, id: string) => cancelDownload(id))
|
||||
ipcMain.handle(IpcChannels.downloadPause, (_e, id: string) => pauseDownload(id))
|
||||
ipcMain.handle(IpcChannels.terminalRun, (e, id: string, args: string) =>
|
||||
runTerminal(e.sender, id, args)
|
||||
)
|
||||
ipcMain.handle(IpcChannels.terminalCancel, (_e, id: string) => cancelTerminal(id))
|
||||
|
||||
ipcMain.handle(IpcChannels.chooseFolder, async (e, current?: string) => {
|
||||
const win = BrowserWindow.fromWebContents(e.sender) ?? undefined
|
||||
// Seed the picker at the currently-configured folder so it opens where the
|
||||
// user already points, not a generic default (W5). 'createDirectory' is a
|
||||
// macOS-only property and a no-op on Windows, so it's dropped (L58).
|
||||
const defaultPath = current && existsSync(current) ? current : undefined
|
||||
// Use the parented overload only when we actually have a window — passing a
|
||||
// forced non-null window that's gone can throw (L53).
|
||||
const res = win
|
||||
? await dialog.showOpenDialog(win, { properties: ['openDirectory'], defaultPath })
|
||||
: await dialog.showOpenDialog({ properties: ['openDirectory'], defaultPath })
|
||||
return res.canceled || !res.filePaths[0] ? null : res.filePaths[0]
|
||||
})
|
||||
|
||||
ipcMain.handle(IpcChannels.openPath, (_e, p: string) => safeOpenPath(p))
|
||||
ipcMain.handle(IpcChannels.openUrl, (_e, url: string) => {
|
||||
try {
|
||||
const { protocol } = new URL(url)
|
||||
if (protocol === 'http:' || protocol === 'https:') void shell.openExternal(url)
|
||||
} catch {
|
||||
// Ignore malformed URLs.
|
||||
}
|
||||
})
|
||||
ipcMain.handle(IpcChannels.showInFolder, (_e, p: string) => safeShowInFolder(p))
|
||||
|
||||
ipcMain.handle(IpcChannels.clipboardRead, () => clipboard.readText())
|
||||
|
||||
ipcMain.handle(IpcChannels.settingsGet, () => getSettings())
|
||||
ipcMain.handle(IpcChannels.settingsSet, (e, partial: Partial<Settings>) => {
|
||||
const result = setSettings(partial)
|
||||
// Keep the window's native background and title bar in sync with the theme
|
||||
// so a compositor repaint never flashes a mismatched color, and the OS-drawn
|
||||
// caption always matches the in-app theme (W3). Use the validated result.
|
||||
if (partial.theme) {
|
||||
BrowserWindow.fromWebContents(e.sender)?.setBackgroundColor(
|
||||
PAGE_BACKGROUND[resolveBackgroundMode(result.theme)]
|
||||
)
|
||||
applyNativeTheme(result.theme)
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
ipcMain.handle(IpcChannels.systemThemeGet, () => getSystemThemeInfo())
|
||||
|
||||
ipcMain.handle(IpcChannels.openHighContrastSettings, () =>
|
||||
shell.openExternal('ms-settings:easeofaccess-highcontrast')
|
||||
)
|
||||
|
||||
ipcMain.handle(IpcChannels.historyList, () => listHistory())
|
||||
ipcMain.handle(IpcChannels.historyAdd, (_e, entry: HistoryEntry) => addHistory(entry))
|
||||
ipcMain.handle(IpcChannels.historyRemove, (_e, id: string) => removeHistory(id))
|
||||
ipcMain.handle(IpcChannels.historyRemoveMany, (_e, ids: string[]) => removeManyHistory(ids))
|
||||
ipcMain.handle(IpcChannels.historyClear, () => clearHistory())
|
||||
|
||||
ipcMain.handle(IpcChannels.cookiesLogin, (e, url: string) =>
|
||||
// Parent the sign-in window to the app window (W6) so it groups under
|
||||
// AeroFetch instead of spawning a second taskbar button.
|
||||
openCookieLoginWindow(url, BrowserWindow.fromWebContents(e.sender) ?? undefined)
|
||||
)
|
||||
ipcMain.handle(IpcChannels.cookiesStatus, () => getCookiesStatus())
|
||||
ipcMain.handle(IpcChannels.cookiesClear, () => clearCookies())
|
||||
|
||||
ipcMain.handle(IpcChannels.templatesList, () => listTemplates())
|
||||
ipcMain.handle(IpcChannels.templatesSave, (_e, template: CommandTemplate) =>
|
||||
saveTemplate(template)
|
||||
)
|
||||
ipcMain.handle(IpcChannels.templatesRemove, (_e, id: string) => removeTemplate(id))
|
||||
|
||||
ipcMain.handle(IpcChannels.commandPreview, (_e, opts: StartDownloadOptions) =>
|
||||
previewCommand(opts)
|
||||
)
|
||||
|
||||
ipcMain.handle(IpcChannels.ytdlpUpdate, (_e, channel: YtdlpUpdateChannel) => updateYtdlp(channel))
|
||||
|
||||
ipcMain.handle(IpcChannels.errorLogList, () => listErrorLog())
|
||||
ipcMain.handle(IpcChannels.errorLogClear, () => clearErrorLog())
|
||||
|
||||
ipcMain.handle(IpcChannels.backupExport, (e) =>
|
||||
exportBackup(BrowserWindow.fromWebContents(e.sender) ?? undefined)
|
||||
)
|
||||
ipcMain.handle(IpcChannels.backupImport, async (e) => {
|
||||
const win = BrowserWindow.fromWebContents(e.sender) ?? undefined
|
||||
const result = await importBackup(win)
|
||||
// A restored backup may have changed the theme; keep the native window
|
||||
// background and title bar in sync the same way settingsSet does (W3).
|
||||
if (result.ok) {
|
||||
const theme = getSettings().theme
|
||||
win?.setBackgroundColor(PAGE_BACKGROUND[resolveBackgroundMode(theme)])
|
||||
applyNativeTheme(theme)
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
// --- Media-manager sources (Pinchflat-style index; see ROADMAP-PINCHFLAT.md) ---
|
||||
ipcMain.handle(IpcChannels.sourcesList, () => listSources())
|
||||
ipcMain.handle(IpcChannels.sourceItems, (_e, sourceId: string) => listMediaItems(sourceId))
|
||||
ipcMain.handle(IpcChannels.sourceRemove, (_e, id: string) => removeSource(id))
|
||||
ipcMain.handle(IpcChannels.sourceItemDownloaded, (_e, id: string, filePath?: string) =>
|
||||
setMediaItemDownloaded(id, filePath)
|
||||
)
|
||||
// Indexing pushes live progress to the requesting renderer over `indexProgress`
|
||||
// and resolves with the final result.
|
||||
ipcMain.handle(IpcChannels.sourceIndex, (e, url: string) =>
|
||||
indexSource(url, (p) => {
|
||||
if (!e.sender.isDestroyed()) e.sender.send(IpcChannels.indexProgress, p)
|
||||
})
|
||||
)
|
||||
ipcMain.handle(IpcChannels.sourceReindex, (e, id: string) => {
|
||||
const src = getSource(id)
|
||||
if (!src) return { ok: false, error: 'Source not found.' }
|
||||
return indexSource(src.url, (p) => {
|
||||
if (!e.sender.isDestroyed()) e.sender.send(IpcChannels.indexProgress, p)
|
||||
})
|
||||
})
|
||||
ipcMain.handle(IpcChannels.sourceSetWatched, (_e, id: string, watched: boolean) =>
|
||||
setSourceWatched(id, watched)
|
||||
)
|
||||
ipcMain.handle(IpcChannels.sourcesSync, (e) =>
|
||||
syncWatchedSources((p) => {
|
||||
if (!e.sender.isDestroyed()) e.sender.send(IpcChannels.indexProgress, p)
|
||||
})
|
||||
)
|
||||
ipcMain.handle(IpcChannels.scheduledSyncGet, () => getScheduledSync())
|
||||
ipcMain.handle(IpcChannels.scheduledSyncSet, (_e, enabled: boolean) => setScheduledSync(enabled))
|
||||
|
||||
// Reflect overall queue progress on the Windows taskbar and window title (SR8).
|
||||
ipcMain.handle(IpcChannels.taskbarProgress, (_e, p: TaskbarProgress) => {
|
||||
if (!mainWindow || mainWindow.isDestroyed()) return
|
||||
if (p.mode === 'none') {
|
||||
mainWindow.setProgressBar(-1)
|
||||
mainWindow.setOverlayIcon(null, '')
|
||||
mainWindow.setTitle('AeroFetch')
|
||||
} else {
|
||||
mainWindow.setProgressBar(Math.max(0, Math.min(1, p.fraction)), { mode: p.mode })
|
||||
const badge = p.mode === 'error' ? getErrorBadge() : getActiveBadge()
|
||||
const n = p.badgeCount ?? 0
|
||||
const label =
|
||||
p.mode === 'error' ? 'Download error' : `${n} download${n !== 1 ? 's' : ''} active`
|
||||
mainWindow.setOverlayIcon(badge, label)
|
||||
mainWindow.setTitle(p.mode === 'error' ? 'AeroFetch — Error' : `AeroFetch — ${label}`)
|
||||
}
|
||||
})
|
||||
|
||||
// Open a YouTube WebView and extract a PO token for bot-check bypass (Phase P).
|
||||
ipcMain.handle(IpcChannels.youtubePoTokenMint, async () => {
|
||||
const token = await openPoTokenWindow()
|
||||
if (token) await setSettings({ youtubePoToken: token })
|
||||
return token
|
||||
})
|
||||
}
|
||||
|
||||
// Push OS theme/contrast changes to every window, and keep the native
|
||||
// background in sync for windows currently following 'system'.
|
||||
function registerSystemThemeBridge(): void {
|
||||
@@ -510,7 +242,7 @@ if (isPrimaryInstance) {
|
||||
optimizer.watchWindowShortcuts(window)
|
||||
})
|
||||
|
||||
registerIpcHandlers()
|
||||
registerIpcHandlers(() => mainWindow)
|
||||
registerSystemThemeBridge()
|
||||
registerSendToShortcut()
|
||||
// Apply the persisted theme to the OS title bar before the window opens (W3).
|
||||
|
||||
Reference in New Issue
Block a user