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>
149 lines
4.4 KiB
TypeScript
149 lines
4.4 KiB
TypeScript
import { BrowserWindow, type WebContents } from 'electron'
|
|
import { LOGIN_PARTITION } from './cookies'
|
|
|
|
/** A public YouTube video used as the extraction target (stable, always accessible). */
|
|
const YT_VIDEO = 'https://www.youtube.com/watch?v=jNQXAC9IVRw'
|
|
|
|
/**
|
|
* Restrict a PO-token window to *.youtube.com and accounts.google.com (for
|
|
* sign-in). Mirrors hardenLoginWebContents in cookies.ts.
|
|
*/
|
|
function isAllowedPoTokenUrl(target: string): boolean {
|
|
try {
|
|
const { protocol, hostname } = new URL(target)
|
|
if (protocol !== 'http:' && protocol !== 'https:') return false
|
|
return (
|
|
hostname === 'youtube.com' ||
|
|
hostname.endsWith('.youtube.com') ||
|
|
hostname === 'accounts.google.com' ||
|
|
hostname.endsWith('.accounts.google.com')
|
|
)
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
function hardenPoTokenWebContents(wc: WebContents): void {
|
|
wc.setWindowOpenHandler((details) => {
|
|
if (!isAllowedPoTokenUrl(details.url)) return { action: 'deny' }
|
|
return {
|
|
action: 'allow',
|
|
overrideBrowserWindowOptions: {
|
|
autoHideMenuBar: true,
|
|
webPreferences: {
|
|
partition: LOGIN_PARTITION,
|
|
sandbox: true,
|
|
contextIsolation: true,
|
|
nodeIntegration: false
|
|
}
|
|
}
|
|
}
|
|
})
|
|
wc.on('will-navigate', (e, navUrl) => {
|
|
if (!isAllowedPoTokenUrl(navUrl)) e.preventDefault()
|
|
})
|
|
wc.on('will-redirect', (e, navUrl) => {
|
|
if (!isAllowedPoTokenUrl(navUrl)) e.preventDefault()
|
|
})
|
|
wc.on('did-create-window', (child) => hardenPoTokenWebContents(child.webContents))
|
|
}
|
|
|
|
/**
|
|
* Async IIFE injected into the YouTube page after load. Polls
|
|
* ytInitialPlayerResponse.serviceIntegrityDimensions.poToken (the field
|
|
* yt-dlp's youtube extractor reads) for up to 8 s, returning the token
|
|
* string or null if it never appears. The poll loop is needed because the
|
|
* player JS initialises asynchronously after the DOM is ready.
|
|
*/
|
|
const EXTRACT_SCRIPT = `
|
|
(async function() {
|
|
for (let i = 0; i < 16; i++) {
|
|
try {
|
|
const pot = window.ytInitialPlayerResponse
|
|
&& window.ytInitialPlayerResponse.serviceIntegrityDimensions
|
|
&& window.ytInitialPlayerResponse.serviceIntegrityDimensions.poToken
|
|
if (typeof pot === 'string' && pot.length > 0) return pot
|
|
} catch (_) {}
|
|
await new Promise(r => setTimeout(r, 500))
|
|
}
|
|
return null
|
|
})()
|
|
`
|
|
|
|
let mintWindow: BrowserWindow | null = null
|
|
|
|
/**
|
|
* Open a visible BrowserWindow on a YouTube video page and extract the
|
|
* Proof-of-Origin token from the page runtime. The window uses the shared
|
|
* login session so a signed-in user's credentials are available.
|
|
*
|
|
* Resolves with the token string on success, or null if the window was closed
|
|
* by the user before extraction completed or if the field was not found.
|
|
*/
|
|
export function openPoTokenWindow(): Promise<string | null> {
|
|
// If a minting window is already open, bring it to the front rather than
|
|
// opening a second one.
|
|
if (mintWindow && !mintWindow.isDestroyed()) {
|
|
mintWindow.focus()
|
|
// Return a promise that resolves when the existing window closes.
|
|
return new Promise((resolve) => {
|
|
mintWindow!.once('closed', () => resolve(null))
|
|
})
|
|
}
|
|
|
|
return new Promise((resolve) => {
|
|
let resolved = false
|
|
const finish = (token: string | null): void => {
|
|
if (resolved) return
|
|
resolved = true
|
|
resolve(token)
|
|
}
|
|
|
|
let win: BrowserWindow
|
|
try {
|
|
win = new BrowserWindow({
|
|
width: 960,
|
|
height: 640,
|
|
title: 'AeroFetch — Fetch YouTube token',
|
|
autoHideMenuBar: true,
|
|
webPreferences: {
|
|
partition: LOGIN_PARTITION,
|
|
sandbox: true,
|
|
contextIsolation: true,
|
|
nodeIntegration: false
|
|
}
|
|
})
|
|
} catch {
|
|
finish(null)
|
|
return
|
|
}
|
|
|
|
mintWindow = win
|
|
hardenPoTokenWebContents(win.webContents)
|
|
win.webContents.session.setPermissionRequestHandler((_wc, _perm, cb) => cb(false))
|
|
|
|
win.webContents.once('did-finish-load', () => {
|
|
win.webContents
|
|
.executeJavaScript(EXTRACT_SCRIPT, true)
|
|
.then((result: unknown) => {
|
|
const token = typeof result === 'string' && result.length > 0 ? result : null
|
|
finish(token)
|
|
win.close()
|
|
})
|
|
.catch(() => {
|
|
finish(null)
|
|
win.close()
|
|
})
|
|
})
|
|
|
|
win.on('closed', () => {
|
|
mintWindow = null
|
|
finish(null)
|
|
})
|
|
|
|
win.loadURL(YT_VIDEO).catch(() => {
|
|
/* navigation errors surface as Chromium's own error page */
|
|
})
|
|
})
|
|
}
|