Compare commits

...

2 Commits

Author SHA1 Message Date
root 8396bdeb48 fix(desktop): disable main/preload minification to keep page.evaluate stable
Desktop Client Build / Resolve Build Metadata (push) Waiting to run
Desktop Client Build / Publish Client Artifacts to NAS (push) Blocked by required conditions
Desktop Client Build / Build Desktop Client (push) Has been cancelled
Playwright serializes the function passed to page.evaluate via .toString()
and runs it in the browser context. With main-process minify on, esbuild
renames inlined helpers (e.g. __name) and closure references to two-letter
identifiers like FK/p/ed; their definitions stay at module scope on the
Node side, so the browser eval throws ReferenceError. Mac dev builds were
unaffected because dev mode doesn't minify; only packaged Windows builds
hit this. Main process bundle size is irrelevant (loaded once on startup)
so turning minify off is the cheapest correct fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 22:30:43 +08:00
root cb42d4b662 fix(desktop): keep oauth popups alive on redirect aborts and blank loads
Toutiao's wap_login redirect chain raises ERR_ABORTED (-3) without the
literal symbol in the message, so the previous regex fell through to the
fallback error page. Now match the numeric form too, and add a 7s blank-
page watchdog that auto-reloads stuck "Loading..." popups once per fresh
navigation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 22:12:28 +08:00
2 changed files with 107 additions and 1 deletions
@@ -29,6 +29,12 @@ export default defineConfig({
plugins: [externalizeDepsPlugin({ exclude: workspacePackages })],
build: {
sourcemap: 'hidden',
// Disable minification for the main process: the bundle is only loaded
// once on app startup so size is irrelevant, and minified short names
// break Playwright `page.evaluate` because the function is serialized
// via `.toString()` and helper identifiers (e.g. `__name` renamed to
// `FK`) become undefined in the browser context.
minify: false,
rollupOptions: {
input: {
bootstrap: resolve(rootDir, 'src/main/bootstrap.ts'),
@@ -51,6 +57,7 @@ export default defineConfig({
plugins: [externalizeDepsPlugin({ exclude: workspacePackages })],
build: {
sourcemap: 'hidden',
minify: false,
rollupOptions: {
input: {
bridge: resolve(rootDir, 'src/preload/bridge.ts'),
+100 -1
View File
@@ -2,16 +2,95 @@ import type { BrowserWindow } from 'electron/main'
import { STANDARD_USER_AGENT } from './user-agent'
const ignoredNavigationFailurePattern = /ERR_ABORTED|ERR_BLOCKED_BY_CLIENT/i
const ignoredNavigationFailurePattern = /ERR_ABORTED|ERR_BLOCKED_BY_CLIENT|\((-3|-20)\) loading/i
const errorPagePrefix = 'data:text/html'
const BLANK_PAGE_PROBE_DELAY_MS = 7_000
const BLANK_PAGE_NODE_THRESHOLD = 3
const BLANK_PAGE_TEXT_THRESHOLD = 5
interface WindowNavigationState {
readyForDetection: boolean
lastMainFrameError: string | null
blankPageWatchdog: NodeJS.Timeout | null
blankPageRetried: boolean
context: string
}
const navigationStateRegistry = new WeakMap<BrowserWindow, WindowNavigationState>()
function clearBlankPageWatchdog(state: WindowNavigationState): void {
if (state.blankPageWatchdog) {
clearTimeout(state.blankPageWatchdog)
state.blankPageWatchdog = null
}
}
async function probePageBodyShape(
window: BrowserWindow,
): Promise<{ nodeCount: number; textLength: number; readyState: string } | null> {
if (window.isDestroyed()) {
return null
}
try {
return (await window.webContents.executeJavaScript(
`(() => ({
nodeCount: document.body ? document.body.querySelectorAll('*').length : 0,
textLength: document.body ? (document.body.innerText || '').trim().length : 0,
readyState: document.readyState,
}))()`,
true,
)) as { nodeCount: number; textLength: number; readyState: string }
} catch {
return null
}
}
function scheduleBlankPageWatchdog(window: BrowserWindow, state: WindowNavigationState): void {
clearBlankPageWatchdog(state)
state.blankPageWatchdog = setTimeout(() => {
state.blankPageWatchdog = null
if (window.isDestroyed()) {
return
}
const currentURL = window.webContents.getURL()
if (currentURL.startsWith(errorPagePrefix) || currentURL === 'about:blank') {
return
}
void probePageBodyShape(window).then((shape) => {
if (!shape || window.isDestroyed()) {
return
}
const looksBlank =
shape.nodeCount < BLANK_PAGE_NODE_THRESHOLD &&
shape.textLength < BLANK_PAGE_TEXT_THRESHOLD
if (!looksBlank) {
return
}
if (state.blankPageRetried) {
console.warn(`[desktop-window] ${state.context} still blank after auto-reload`, {
url: currentURL,
...shape,
})
return
}
state.blankPageRetried = true
console.warn(`[desktop-window] ${state.context} blank page detected, reloading once`, {
url: currentURL,
...shape,
})
window.webContents.reloadIgnoringCache()
})
}, BLANK_PAGE_PROBE_DELAY_MS)
}
function escapeHtml(value: string): string {
return value
.replaceAll('&', '&amp;')
@@ -126,6 +205,9 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
navigationStateRegistry.set(window, {
readyForDetection: false,
lastMainFrameError: null,
blankPageWatchdog: null,
blankPageRetried: false,
context,
})
window.webContents.on('did-start-loading', () => {
@@ -134,6 +216,7 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
return
}
state.readyForDetection = false
clearBlankPageWatchdog(state)
})
window.webContents.on(
@@ -147,6 +230,7 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
if (state) {
state.readyForDetection = false
state.lastMainFrameError = `${errorCode}:${errorDescription}`
clearBlankPageWatchdog(state)
}
console.warn(`[desktop-window] ${context} main-frame load failed`, {
@@ -167,6 +251,7 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
state.readyForDetection = !currentURL.startsWith(errorPagePrefix)
if (state.readyForDetection) {
state.lastMainFrameError = null
scheduleBlankPageWatchdog(window, state)
}
})
@@ -174,10 +259,18 @@ export function attachWindowDiagnostics(window: BrowserWindow, context: string):
const state = navigationStateRegistry.get(window)
if (state) {
state.readyForDetection = false
clearBlankPageWatchdog(state)
}
console.error(`[desktop-window] ${context} renderer gone`, details)
})
window.once('closed', () => {
const state = navigationStateRegistry.get(window)
if (state) {
clearBlankPageWatchdog(state)
}
})
}
export function isWindowReadyForDetection(window: BrowserWindow): boolean {
@@ -193,6 +286,12 @@ export async function loadWindowURLSafely(
targetURL: string,
context: string,
): Promise<void> {
const state = navigationStateRegistry.get(window)
if (state) {
state.blankPageRetried = false
clearBlankPageWatchdog(state)
}
try {
await window.loadURL(targetURL, { userAgent: STANDARD_USER_AGENT })
} catch (error) {