From c5a6d765eedddd1ef203c1cfa0ce9729ac5f617d Mon Sep 17 00:00:00 2001 From: tisonhuang Date: Thu, 5 Mar 2026 11:15:44 +0800 Subject: [PATCH] fix(auth): avoid logout on export-only clear and harden db key auto-fetch --- electron/main.ts | 40 ++++++++++---------- electron/services/keyService.ts | 67 ++++++++++++++++++++++++++++++++- src/components/Sidebar.tsx | 9 ++++- src/pages/SettingsPage.tsx | 17 ++++++++- src/pages/WelcomePage.tsx | 17 ++++++++- 5 files changed, 126 insertions(+), 24 deletions(-) diff --git a/electron/main.ts b/electron/main.ts index 0581e17..a2d8d58 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -1347,27 +1347,29 @@ function registerIpcHandlers() { } } - try { - const wxidConfigsRaw = cfg.get('wxidConfigs') as Record | undefined - if (wxidConfigsRaw && typeof wxidConfigsRaw === 'object') { - const nextConfigs: Record = { ...wxidConfigsRaw } - for (const key of Object.keys(nextConfigs)) { - if (isMatchedAccountName(key) || normalizeAccountId(key) === normalizedWxid) { - delete nextConfigs[key] + if (clearCache) { + try { + const wxidConfigsRaw = cfg.get('wxidConfigs') as Record | undefined + if (wxidConfigsRaw && typeof wxidConfigsRaw === 'object') { + const nextConfigs: Record = { ...wxidConfigsRaw } + for (const key of Object.keys(nextConfigs)) { + if (isMatchedAccountName(key) || normalizeAccountId(key) === normalizedWxid) { + delete nextConfigs[key] + } } + cfg.set('wxidConfigs' as any, nextConfigs as any) } - cfg.set('wxidConfigs' as any, nextConfigs as any) + cfg.set('myWxid' as any, '') + cfg.set('decryptKey' as any, '') + cfg.set('imageXorKey' as any, 0) + cfg.set('imageAesKey' as any, '') + cfg.set('dbPath' as any, '') + cfg.set('lastOpenedDb' as any, '') + cfg.set('onboardingDone' as any, false) + cfg.set('lastSession' as any, '') + } catch (error) { + warnings.push(`清理账号配置失败: ${String(error)}`) } - cfg.set('myWxid' as any, '') - cfg.set('decryptKey' as any, '') - cfg.set('imageXorKey' as any, 0) - cfg.set('imageAesKey' as any, '') - cfg.set('dbPath' as any, '') - cfg.set('lastOpenedDb' as any, '') - cfg.set('onboardingDone' as any, false) - cfg.set('lastSession' as any, '') - } catch (error) { - warnings.push(`清理账号配置失败: ${String(error)}`) } return { @@ -2172,7 +2174,7 @@ function registerIpcHandlers() { // 密钥获取 ipcMain.handle('key:autoGetDbKey', async (event) => { - return keyService.autoGetDbKey(60_000, (message, level) => { + return keyService.autoGetDbKey(180_000, (message, level) => { event.sender.send('key:dbKeyStatus', { message, level }) }) }) diff --git a/electron/services/keyService.ts b/electron/services/keyService.ts index 0b4a67b..376f7ec 100644 --- a/electron/services/keyService.ts +++ b/electron/services/keyService.ts @@ -509,6 +509,58 @@ export class KeyService { return false } + private isLoginRelatedText(value: string): boolean { + const normalized = String(value || '').replace(/\s+/g, '').toLowerCase() + if (!normalized) return false + const keywords = [ + '登录', + '扫码', + '二维码', + '请在手机上确认', + '手机确认', + '切换账号', + 'wechatlogin', + 'qrcode', + 'scan' + ] + return keywords.some((keyword) => normalized.includes(keyword)) + } + + private async detectWeChatLoginRequired(pid: number): Promise { + if (!this.ensureUser32()) return false + let loginRequired = false + + const enumWindowsCallback = this.koffi.register((hWnd: any, _lParam: any) => { + if (!this.IsWindowVisible(hWnd)) return true + const title = this.getWindowTitle(hWnd) + if (!this.isWeChatWindowTitle(title)) return true + + const pidBuf = Buffer.alloc(4) + this.GetWindowThreadProcessId(hWnd, pidBuf) + const windowPid = pidBuf.readUInt32LE(0) + if (windowPid !== pid) return true + + if (this.isLoginRelatedText(title)) { + loginRequired = true + return false + } + + const children = this.collectChildWindowInfos(hWnd) + for (const child of children) { + if (this.isLoginRelatedText(child.title) || this.isLoginRelatedText(child.className)) { + loginRequired = true + return false + } + } + return true + }, this.WNDENUMPROC_PTR) + + this.EnumWindows(enumWindowsCallback, 0) + this.koffi.unregister(enumWindowsCallback) + + return loginRequired + } + private async waitForWeChatWindowComponents(pid: number, timeoutMs = 15000): Promise { if (!this.ensureUser32()) return true const startTime = Date.now() @@ -605,6 +657,7 @@ export class KeyService { const keyBuffer = Buffer.alloc(128) const start = Date.now() + let loginRequiredDetected = false try { while (Date.now() - start < timeoutMs) { @@ -624,6 +677,9 @@ export class KeyService { const level = levelOut[0] ?? 0 if (msg) { logs.push(msg) + if (this.isLoginRelatedText(msg)) { + loginRequiredDetected = true + } onStatus?.(msg, level) } } @@ -635,6 +691,15 @@ export class KeyService { } catch { } } + const loginRequired = loginRequiredDetected || await this.detectWeChatLoginRequired(pid) + if (loginRequired) { + return { + success: false, + error: '微信已启动但尚未完成登录,请先在微信客户端完成登录后再重试自动获取密钥。', + logs + } + } + return { success: false, error: '获取密钥超时', logs } } @@ -983,4 +1048,4 @@ export class KeyService { return false } catch { return false } } -} \ No newline at end of file +} diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 009d235..1022607 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -305,9 +305,14 @@ function Sidebar() { if (result.warning) { detailLines.push('', `注意:${result.warning}`) } - window.alert(`账号数据清理完成。\n\n${detailLines.join('\n')}\n\n为保障数据安全,WeFlow 已清除该账号本地缓存/导出相关数据。若需再次获取数据,请手动登录微信客户端并重新在 WeFlow 完成配置。`) + const followupHint = shouldClearCacheData + ? '若需再次获取数据,请手动登录微信客户端并重新在 WeFlow 完成配置。' + : '你可以继续使用当前登录状态,无需重新登录。' + window.alert(`账号数据清理完成。\n\n${detailLines.join('\n')}\n\n为保障数据安全,WeFlow 已清除该账号本地缓存/导出相关数据。${followupHint}`) resetClearDialogState() - window.location.reload() + if (shouldClearCacheData) { + window.location.reload() + } } catch (error) { console.error('清理账号数据失败:', error) window.alert('清理失败,请稍后重试。') diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index 368bed5..9d3fdc4 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -36,6 +36,18 @@ interface WxidOption { modifiedTime: number } +const formatDbKeyFailureMessage = (error?: string, logs?: string[]): string => { + const base = String(error || '自动获取密钥失败').trim() + const tailLogs = Array.isArray(logs) + ? logs + .map(item => String(item || '').trim()) + .filter(Boolean) + .slice(-6) + : [] + if (tailLogs.length === 0) return base + return `${base};最近状态:${tailLogs.join(' | ')}` +} + function SettingsPage() { const { isDbConnected, @@ -725,7 +737,10 @@ function SettingsPage() { setIsManualStartPrompt(true) setDbKeyStatus('需要手动启动微信') } else { - showMessage(result.error || '自动获取密钥失败', false) + if (result.error?.includes('尚未完成登录')) { + setDbKeyStatus('请先在微信完成登录后重试') + } + showMessage(formatDbKeyFailureMessage(result.error, result.logs), false) } } } catch (e: any) { diff --git a/src/pages/WelcomePage.tsx b/src/pages/WelcomePage.tsx index 5e61b65..9c1d62c 100644 --- a/src/pages/WelcomePage.tsx +++ b/src/pages/WelcomePage.tsx @@ -23,6 +23,18 @@ interface WelcomePageProps { standalone?: boolean } +const formatDbKeyFailureMessage = (error?: string, logs?: string[]): string => { + const base = String(error || '自动获取密钥失败').trim() + const tailLogs = Array.isArray(logs) + ? logs + .map(item => String(item || '').trim()) + .filter(Boolean) + .slice(-6) + : [] + if (tailLogs.length === 0) return base + return `${base};最近状态:${tailLogs.join(' | ')}` +} + function WelcomePage({ standalone = false }: WelcomePageProps) { const navigate = useNavigate() const { isDbConnected, setDbConnected, setLoading } = useAppStore() @@ -292,7 +304,10 @@ function WelcomePage({ standalone = false }: WelcomePageProps) { setIsManualStartPrompt(true) setDbKeyStatus('需要手动启动微信') } else { - setError(result.error || '自动获取密钥失败') + if (result.error?.includes('尚未完成登录')) { + setDbKeyStatus('请先在微信完成登录后重试') + } + setError(formatDbKeyFailureMessage(result.error, result.logs)) } } } catch (e) {