页面美化
This commit is contained in:
+122
-15
@@ -25,19 +25,31 @@ platformServiceFactory.register(new XiaoheiheService())
|
||||
// Persistent browser context for maintaining login state
|
||||
let persistentContext: BrowserContext | null = null
|
||||
const userDataDir = join(app.getPath('userData'), 'browser-data')
|
||||
let contextInitializing = false
|
||||
let contextInitRetries = 0
|
||||
const MAX_CONTEXT_INIT_RETRIES = 3
|
||||
|
||||
// Settings file path
|
||||
const settingsDir = join(app.getPath('userData'), 'settings')
|
||||
const settingsFilePath = join(settingsDir, 'config.json')
|
||||
const loginInfoFilePath = join(settingsDir, 'login-info.json')
|
||||
|
||||
// Constants
|
||||
const RATE_LIMIT_CONFIG = {
|
||||
MIN_INTERVAL_MS: 3000, // 最小间隔 3 秒
|
||||
MAX_SEARCH_PER_MINUTE: 10, // 每分钟最多 10 次
|
||||
RESET_INTERVAL_MS: 60000, // 1 分钟重置计数
|
||||
PAGE_TIMEOUT_MS: 30000, // 页面加载超时 30 秒
|
||||
BROWSER_INIT_TIMEOUT_MS: 30000 // 浏览器初始化超时 30 秒
|
||||
} as const
|
||||
|
||||
// Rate limiter for search operations
|
||||
class SearchRateLimiter {
|
||||
private lastSearchTime: number = 0
|
||||
private searchCount: number = 0
|
||||
private readonly minInterval: number = 3000 // 最小间隔 3 秒
|
||||
private readonly maxSearchPerMinute: number = 10 // 每分钟最多 10 次
|
||||
private readonly resetInterval: number = 60000 // 1 分钟重置计数
|
||||
private readonly minInterval: number = RATE_LIMIT_CONFIG.MIN_INTERVAL_MS
|
||||
private readonly maxSearchPerMinute: number = RATE_LIMIT_CONFIG.MAX_SEARCH_PER_MINUTE
|
||||
private readonly resetInterval: number = RATE_LIMIT_CONFIG.RESET_INTERVAL_MS
|
||||
|
||||
canSearch(): { allowed: boolean; waitTime?: number; reason?: string } {
|
||||
const now = Date.now()
|
||||
@@ -241,12 +253,14 @@ function createChatWindow(initialText?: string): void {
|
||||
height: 600,
|
||||
title: 'AI 对话',
|
||||
show: false, // Hide window during load for better perceived performance
|
||||
backgroundColor: '#ffffff', // Set background color to avoid flash
|
||||
webPreferences: {
|
||||
preload: join(__dirname, '../preload/index.js'),
|
||||
sandbox: false,
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
spellcheck: false // Disable spell check to avoid conflicts with IME
|
||||
spellcheck: false, // Disable spell check to avoid conflicts with IME
|
||||
backgroundThrottling: false // Prevent throttling for better performance
|
||||
}
|
||||
})
|
||||
|
||||
@@ -362,7 +376,7 @@ async function fetchArticleContent(url: string): Promise<{
|
||||
|
||||
console.log('fetchArticleContent: Navigating to URL...')
|
||||
// Navigate to the URL
|
||||
await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 })
|
||||
await page.goto(url, { waitUntil: 'networkidle', timeout: RATE_LIMIT_CONFIG.PAGE_TIMEOUT_MS })
|
||||
|
||||
// Get appropriate scraper for this URL
|
||||
console.log('fetchArticleContent: Getting scraper for URL...')
|
||||
@@ -394,15 +408,97 @@ async function fetchArticleContent(url: string): Promise<{
|
||||
|
||||
// Get or create persistent browser context
|
||||
async function getPersistentContext(headless = true): Promise<BrowserContext> {
|
||||
if (!persistentContext) {
|
||||
persistentContext = await chromium.launchPersistentContext(userDataDir, {
|
||||
headless, // 根据参数决定是否无头模式
|
||||
viewport: { width: 1280, height: 800 },
|
||||
userAgent:
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
||||
})
|
||||
// Return existing context if available and not closed
|
||||
if (persistentContext && !persistentContext.pages().length) {
|
||||
console.log('Browser context exists but has no pages, reinitializing')
|
||||
try {
|
||||
await persistentContext.close()
|
||||
} catch (error) {
|
||||
console.error('Error closing stale context:', error)
|
||||
}
|
||||
persistentContext = null
|
||||
}
|
||||
return persistentContext
|
||||
|
||||
if (persistentContext) {
|
||||
try {
|
||||
// Test if context is still alive
|
||||
await persistentContext.pages()
|
||||
return persistentContext
|
||||
} catch (error) {
|
||||
console.error('Browser context is dead, reinitializing:', error)
|
||||
persistentContext = null
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent multiple simultaneous initialization attempts
|
||||
if (contextInitializing) {
|
||||
console.log('Context initialization in progress, waiting...')
|
||||
// Wait for initialization to complete
|
||||
while (contextInitializing && contextInitRetries < MAX_CONTEXT_INIT_RETRIES) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
}
|
||||
if (persistentContext) {
|
||||
return persistentContext
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize new context with retry logic
|
||||
contextInitializing = true
|
||||
let lastError: Error | null = null
|
||||
|
||||
for (let attempt = 1; attempt <= MAX_CONTEXT_INIT_RETRIES; attempt++) {
|
||||
try {
|
||||
console.log(`Initializing browser context (attempt ${attempt}/${MAX_CONTEXT_INIT_RETRIES})`)
|
||||
|
||||
persistentContext = await chromium.launchPersistentContext(userDataDir, {
|
||||
headless,
|
||||
viewport: { width: 1280, height: 800 },
|
||||
userAgent:
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
// Add timeout for initialization
|
||||
timeout: RATE_LIMIT_CONFIG.BROWSER_INIT_TIMEOUT_MS
|
||||
})
|
||||
|
||||
// Setup crash handler
|
||||
persistentContext.on('close', () => {
|
||||
console.log('Browser context closed')
|
||||
persistentContext = null
|
||||
contextInitRetries = 0
|
||||
})
|
||||
|
||||
console.log('Browser context initialized successfully')
|
||||
contextInitializing = false
|
||||
contextInitRetries = 0
|
||||
return persistentContext
|
||||
} catch (error) {
|
||||
lastError = error as Error
|
||||
console.error(`Browser context initialization failed (attempt ${attempt}):`, error)
|
||||
|
||||
// Clean up failed context
|
||||
if (persistentContext) {
|
||||
try {
|
||||
await persistentContext.close()
|
||||
} catch (closeError) {
|
||||
console.error('Error closing failed context:', closeError)
|
||||
}
|
||||
persistentContext = null
|
||||
}
|
||||
|
||||
// Wait before retry (exponential backoff)
|
||||
if (attempt < MAX_CONTEXT_INIT_RETRIES) {
|
||||
const delayMs = 1000 * attempt
|
||||
console.log(`Waiting ${delayMs}ms before retry...`)
|
||||
await new Promise(resolve => setTimeout(resolve, delayMs))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contextInitializing = false
|
||||
contextInitRetries++
|
||||
|
||||
throw new Error(
|
||||
`Failed to initialize browser context after ${MAX_CONTEXT_INIT_RETRIES} attempts: ${lastError?.message}`
|
||||
)
|
||||
}
|
||||
|
||||
// Get login QR code
|
||||
@@ -522,7 +618,7 @@ async function checkPlatformLogin(url: string): Promise<{
|
||||
|
||||
const context = await getPersistentContext()
|
||||
page = await context.newPage()
|
||||
await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 })
|
||||
await page.goto(url, { waitUntil: 'networkidle', timeout: RATE_LIMIT_CONFIG.PAGE_TIMEOUT_MS })
|
||||
|
||||
const loginStatus = await service.checkLoginStatus(page)
|
||||
|
||||
@@ -857,8 +953,19 @@ app.on('window-all-closed', () => {
|
||||
})
|
||||
|
||||
// Unregister all shortcuts when app is about to quit
|
||||
app.on('will-quit', () => {
|
||||
app.on('will-quit', async () => {
|
||||
globalShortcut.unregisterAll()
|
||||
|
||||
// Clean up browser context
|
||||
if (persistentContext) {
|
||||
console.log('Cleaning up browser context on app quit')
|
||||
try {
|
||||
await persistentContext.close()
|
||||
persistentContext = null
|
||||
} catch (error) {
|
||||
console.error('Error closing browser context on quit:', error)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// In this file you can include the rest of your app's specific main process
|
||||
|
||||
Reference in New Issue
Block a user