mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-21 14:03:35 +08:00
fix(desktop): reveal renderer startup failures (#1032)
This commit is contained in:
parent
e72020247b
commit
26e863902f
@ -1,10 +1,10 @@
|
|||||||
import { app, BrowserWindow, clipboard, ipcMain, Notification, screen, session, WebContentsView } from 'electron'
|
import { app, BrowserWindow, clipboard, dialog, ipcMain, Notification, screen, session, WebContentsView } from 'electron'
|
||||||
import { autoUpdater } from 'electron-updater'
|
import { autoUpdater } from 'electron-updater'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { ELECTRON_EVENT_CHANNELS, ELECTRON_INTERNAL_CHANNELS, ELECTRON_IPC_CHANNELS, type ElectronIpcChannel } from './ipc/channels'
|
import { ELECTRON_EVENT_CHANNELS, ELECTRON_INTERNAL_CHANNELS, ELECTRON_IPC_CHANNELS, type ElectronIpcChannel } from './ipc/channels'
|
||||||
import { isElectronIpcChannel, validateElectronIpcPayload } from './ipc/capabilities'
|
import { isElectronIpcChannel, validateElectronIpcPayload } from './ipc/capabilities'
|
||||||
import { ElectronServerRuntime } from './services/serverRuntime'
|
import { ElectronServerRuntime } from './services/serverRuntime'
|
||||||
import { electronHostDiagnosticsFile } from './services/sidecarManager'
|
import { electronHostDiagnosticsFile, sanitizeHostDiagnostic } from './services/sidecarManager'
|
||||||
import { openDialog, saveDialog } from './services/dialogs'
|
import { openDialog, saveDialog } from './services/dialogs'
|
||||||
import { openExternalUrl, openSystemPath, openSystemSettingsUrl } from './services/shell'
|
import { openExternalUrl, openSystemPath, openSystemSettingsUrl } from './services/shell'
|
||||||
import {
|
import {
|
||||||
@ -38,6 +38,7 @@ import { logNotificationSmokeRendererAck, scheduleNotificationSmoke } from './se
|
|||||||
import { normalizeZoomFactor } from './services/zoom'
|
import { normalizeZoomFactor } from './services/zoom'
|
||||||
import { resolveRendererEntry } from './services/rendererEntry'
|
import { resolveRendererEntry } from './services/rendererEntry'
|
||||||
import { writeWindowSmokeSnapshot } from './services/windowSmoke'
|
import { writeWindowSmokeSnapshot } from './services/windowSmoke'
|
||||||
|
import { loadAndRevealMainWindow } from './services/windowStartup'
|
||||||
import {
|
import {
|
||||||
installWindowLifecycle,
|
installWindowLifecycle,
|
||||||
readWindowState,
|
readWindowState,
|
||||||
@ -410,10 +411,20 @@ async function createMainWindow() {
|
|||||||
|
|
||||||
writeWindowSmokeSnapshot(mainWindow, 'after-create')
|
writeWindowSmokeSnapshot(mainWindow, 'after-create')
|
||||||
|
|
||||||
await loadRendererEntry(mainWindow)
|
await loadAndRevealMainWindow({
|
||||||
|
load: () => loadRendererEntry(mainWindow!),
|
||||||
restoreWindowMaximized(mainWindow, restoredState)
|
beforeReveal: () => restoreWindowMaximized(mainWindow!, restoredState),
|
||||||
showMainWindow(mainWindow, app)
|
reveal: () => showMainWindow(mainWindow, app),
|
||||||
|
onLoadFailure: (error) => {
|
||||||
|
const detail = sanitizeHostDiagnostic(error instanceof Error ? error.message : String(error))
|
||||||
|
console.error(`[desktop] failed to load Electron renderer: ${detail}`)
|
||||||
|
writeWindowSmokeSnapshot(mainWindow, 'renderer-load-failed')
|
||||||
|
dialog.showErrorBox(
|
||||||
|
'启动错误 / Startup Error',
|
||||||
|
`桌面界面加载失败,请重启应用。如果问题持续存在,请附上诊断日志反馈。\n\nThe desktop interface could not be loaded. Restart the app and include diagnostics when reporting the problem.\n\n${detail}`,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
refreshWindowsDragHitTest(mainWindow, process.platform)
|
refreshWindowsDragHitTest(mainWindow, process.platform)
|
||||||
writeWindowSmokeSnapshot(mainWindow, 'after-final-show')
|
writeWindowSmokeSnapshot(mainWindow, 'after-final-show')
|
||||||
}
|
}
|
||||||
|
|||||||
57
desktop/electron/services/windowStartup.test.ts
Normal file
57
desktop/electron/services/windowStartup.test.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
import { loadAndRevealMainWindow } from './windowStartup'
|
||||||
|
|
||||||
|
describe('Electron main window startup', () => {
|
||||||
|
it('reveals the main window after the renderer loads', async () => {
|
||||||
|
const order: string[] = []
|
||||||
|
const onLoadFailure = vi.fn()
|
||||||
|
|
||||||
|
const result = await loadAndRevealMainWindow({
|
||||||
|
load: async () => { order.push('load') },
|
||||||
|
beforeReveal: () => { order.push('before-reveal') },
|
||||||
|
reveal: () => { order.push('reveal') },
|
||||||
|
onLoadFailure,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual({ loaded: true })
|
||||||
|
expect(order).toEqual(['load', 'before-reveal', 'reveal'])
|
||||||
|
expect(onLoadFailure).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('still reveals the main window and reports the error when renderer loading fails', async () => {
|
||||||
|
const order: string[] = []
|
||||||
|
const failure = new Error('renderer entry unavailable')
|
||||||
|
|
||||||
|
const result = await loadAndRevealMainWindow({
|
||||||
|
load: async () => {
|
||||||
|
order.push('load')
|
||||||
|
throw failure
|
||||||
|
},
|
||||||
|
beforeReveal: () => { order.push('before-reveal') },
|
||||||
|
reveal: () => { order.push('reveal') },
|
||||||
|
onLoadFailure: (error) => {
|
||||||
|
expect(error).toBe(failure)
|
||||||
|
order.push('failure')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual({ loaded: false, error: failure })
|
||||||
|
expect(order).toEqual(['load', 'before-reveal', 'reveal', 'failure'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('treats an undefined rejection as a load failure', async () => {
|
||||||
|
const reveal = vi.fn()
|
||||||
|
const onLoadFailure = vi.fn()
|
||||||
|
|
||||||
|
const result = await loadAndRevealMainWindow({
|
||||||
|
load: () => Promise.reject(undefined),
|
||||||
|
beforeReveal: vi.fn(),
|
||||||
|
reveal,
|
||||||
|
onLoadFailure,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(result).toEqual({ loaded: false, error: undefined })
|
||||||
|
expect(reveal).toHaveBeenCalledTimes(1)
|
||||||
|
expect(onLoadFailure).toHaveBeenCalledWith(undefined)
|
||||||
|
})
|
||||||
|
})
|
||||||
35
desktop/electron/services/windowStartup.ts
Normal file
35
desktop/electron/services/windowStartup.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
export type MainWindowStartupOptions = {
|
||||||
|
load: () => Promise<void>
|
||||||
|
beforeReveal: () => void
|
||||||
|
reveal: () => void
|
||||||
|
onLoadFailure: (error: unknown) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MainWindowStartupResult =
|
||||||
|
| { loaded: true }
|
||||||
|
| { loaded: false, error: unknown }
|
||||||
|
|
||||||
|
export async function loadAndRevealMainWindow({
|
||||||
|
load,
|
||||||
|
beforeReveal,
|
||||||
|
reveal,
|
||||||
|
onLoadFailure,
|
||||||
|
}: MainWindowStartupOptions): Promise<MainWindowStartupResult> {
|
||||||
|
let loaded = true
|
||||||
|
let loadError: unknown
|
||||||
|
try {
|
||||||
|
await load()
|
||||||
|
} catch (error) {
|
||||||
|
loaded = false
|
||||||
|
loadError = error
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeReveal()
|
||||||
|
reveal()
|
||||||
|
|
||||||
|
if (!loaded) {
|
||||||
|
onLoadFailure(loadError)
|
||||||
|
return { loaded: false, error: loadError }
|
||||||
|
}
|
||||||
|
return { loaded: true }
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user