fix: harden desktop monitoring recovery
Desktop Client Build / Resolve Build Metadata (push) Successful in 37s
Backend CI / Backend (push) Failing after 9m26s
Desktop Client Build / Build Desktop Client (push) Successful in 22m29s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 35s
Desktop Client Build / Resolve Build Metadata (push) Successful in 37s
Backend CI / Backend (push) Failing after 9m26s
Desktop Client Build / Build Desktop Client (push) Successful in 22m29s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 35s
This commit is contained in:
@@ -53,7 +53,7 @@ import {
|
||||
installObservedGlobalFetch,
|
||||
registerObservedRequestRendererTarget,
|
||||
} from './network-observer'
|
||||
import { getPlaywrightCDPPort, startHiddenPlaywrightReaper } from './playwright-cdp'
|
||||
import { getPlaywrightCDPPort, preparePlaywrightCDPPort, startHiddenPlaywrightReaper } from './playwright-cdp'
|
||||
import { initProcessMetricsSampler } from './process-metrics'
|
||||
import { registerRendererDevtoolsProxyTarget } from './renderer-devtools-proxy'
|
||||
import {
|
||||
@@ -112,8 +112,8 @@ app.commandLine.appendSwitch(
|
||||
)
|
||||
app.commandLine.appendSwitch('disable-quic')
|
||||
app.commandLine.appendSwitch('disable-background-networking')
|
||||
// CDP is required by the hidden Playwright execution pool in packaged builds.
|
||||
app.commandLine.appendSwitch('remote-debugging-port', String(getPlaywrightCDPPort()))
|
||||
// CDP is required by the hidden Playwright execution pool. Electron only reads
|
||||
// this switch during early startup, so select the port before app.whenReady().
|
||||
app.userAgentFallback = STANDARD_USER_AGENT
|
||||
|
||||
// Windows 10+ routes tray.displayBalloon through the Toast Notification API,
|
||||
@@ -151,6 +151,7 @@ console.info('[desktop-main] boot', {
|
||||
chrome: process.versions.chrome,
|
||||
node: process.versions.node,
|
||||
ua: STANDARD_USER_AGENT,
|
||||
cdpPort: getPlaywrightCDPPort(),
|
||||
})
|
||||
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
@@ -166,6 +167,7 @@ let loginWindow: ElectronBrowserWindow | null = null
|
||||
let settingsWindow: ElectronBrowserWindow | null = null
|
||||
let mainRendererContents: ElectronWebContents | null = null
|
||||
let quitReleaseInFlight = false
|
||||
let cdpRecoveryRelaunchInFlight = false
|
||||
let quitAfterSaasLogoutPromise: Promise<void> | null = null
|
||||
let mainWindowCreatePromise: Promise<ElectronBrowserWindow> | null = null
|
||||
let loginWindowCreatePromise: Promise<ElectronBrowserWindow> | null = null
|
||||
@@ -176,6 +178,10 @@ let canHandleDesktopDeepLinks = false
|
||||
const pendingDesktopDeepLinks: string[] = []
|
||||
const forceCloseWindows = new WeakSet<ElectronBrowserWindow>()
|
||||
const appWindowModes = new WeakMap<ElectronBrowserWindow, RendererWindowMode>()
|
||||
const maxCDPRecoveryRelaunchesPerWindow = 3
|
||||
const cdpRecoveryRelaunchWindowMs = 10 * 60_000
|
||||
const cdpRecoveryRelaunchDelayMs = 1_500
|
||||
const cdpFatalArg = '--geo-cdp-fatal'
|
||||
|
||||
type WindowMode = 'login' | 'main'
|
||||
type RendererWindowMode = WindowMode | 'settings'
|
||||
@@ -547,6 +553,50 @@ function quitAfterClearingSaasSession(): void {
|
||||
})
|
||||
}
|
||||
|
||||
function requestPlaywrightCDPRecoveryRelaunch(message: string): void {
|
||||
if (cdpRecoveryRelaunchInFlight) {
|
||||
return
|
||||
}
|
||||
|
||||
const now = Date.now()
|
||||
const recentRelaunches = process.argv
|
||||
.filter((arg) => arg.startsWith('--geo-cdp-recovery-at='))
|
||||
.map((arg) => Number.parseInt(arg.split('=')[1] ?? '', 10))
|
||||
.filter((timestamp) => Number.isFinite(timestamp) && now - timestamp < cdpRecoveryRelaunchWindowMs)
|
||||
|
||||
if (recentRelaunches.length >= maxCDPRecoveryRelaunchesPerWindow) {
|
||||
console.error('[desktop-main] playwright cdp recovery relaunch suppressed', {
|
||||
message,
|
||||
recentRelaunches: recentRelaunches.length,
|
||||
})
|
||||
showTrayBalloon(
|
||||
'浏览器自动化通道异常',
|
||||
'客户端已停止采集。请重启客户端或检查安全软件/端口占用。',
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
cdpRecoveryRelaunchInFlight = true
|
||||
console.warn('[desktop-main] scheduling playwright cdp recovery relaunch', {
|
||||
message,
|
||||
cdpPort: getPlaywrightCDPPort(),
|
||||
})
|
||||
showTrayBalloon('正在恢复浏览器自动化通道', '客户端将自动重启后台进程,登录状态会保留。')
|
||||
|
||||
setTimeout(() => {
|
||||
quitReleaseInFlight = true
|
||||
app.relaunch({
|
||||
args: [
|
||||
...process.argv.slice(1).filter((arg) => !arg.startsWith('--geo-cdp-recovery-at=')),
|
||||
...recentRelaunches.map((timestamp) => `--geo-cdp-recovery-at=${timestamp}`),
|
||||
`--geo-cdp-recovery-at=${now}`,
|
||||
...(recentRelaunches.length + 1 >= maxCDPRecoveryRelaunchesPerWindow ? [cdpFatalArg] : []),
|
||||
],
|
||||
})
|
||||
app.exit(0)
|
||||
}, cdpRecoveryRelaunchDelayMs)
|
||||
}
|
||||
|
||||
function windowFromIpcEvent(event: IpcMainInvokeEvent): ElectronBrowserWindow | null {
|
||||
const window = BrowserWindow.fromWebContents(event.sender)
|
||||
if (!window || window.isDestroyed()) {
|
||||
@@ -1197,9 +1247,15 @@ if (!hasSingleInstanceLock) {
|
||||
console.info('[desktop-main] another instance is already running; exiting')
|
||||
app.exit(0)
|
||||
} else {
|
||||
app
|
||||
.whenReady()
|
||||
preparePlaywrightCDPPort()
|
||||
.then((selectedPlaywrightCDPPort) => {
|
||||
app.commandLine.appendSwitch('remote-debugging-port', String(selectedPlaywrightCDPPort))
|
||||
return app.whenReady()
|
||||
})
|
||||
.then(async () => {
|
||||
console.info('[desktop-main] playwright cdp port selected', {
|
||||
cdpPort: getPlaywrightCDPPort(),
|
||||
})
|
||||
suppressNativeWindowMenu()
|
||||
registerDesktopProtocolClient()
|
||||
currentWindowMode = readPersistedWindowMode()
|
||||
@@ -1215,6 +1271,15 @@ if (!hasSingleInstanceLock) {
|
||||
startHiddenPlaywrightReaper()
|
||||
onRuntimeInvalidated((event) => {
|
||||
syncDesktopHealthIndicator(currentMainWindow())
|
||||
if (event.reason === 'playwright-cdp-fatal') {
|
||||
showTrayBalloon(
|
||||
'浏览器自动化组件不可用',
|
||||
event.message ?? '客户端已停止采集,请重启客户端或检查安全软件/端口占用。',
|
||||
)
|
||||
}
|
||||
if (event.reason === 'playwright-cdp-recovery') {
|
||||
requestPlaywrightCDPRecoveryRelaunch(event.message ?? 'playwright_cdp_endpoint_unavailable')
|
||||
}
|
||||
if (!mainRendererContents || mainRendererContents.isDestroyed()) {
|
||||
return
|
||||
}
|
||||
@@ -1234,7 +1299,7 @@ if (!hasSingleInstanceLock) {
|
||||
flushPendingDesktopDeepLinks()
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('[desktop-main] app.whenReady failed', error)
|
||||
console.error('[desktop-main] startup failed', error)
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
|
||||
Reference in New Issue
Block a user