fix(auth): avoid logout on export-only clear and harden db key auto-fetch

This commit is contained in:
tisonhuang
2026-03-05 11:15:44 +08:00
parent 459f23bbd6
commit c5a6d765ee
5 changed files with 126 additions and 24 deletions

View File

@@ -1347,27 +1347,29 @@ function registerIpcHandlers() {
}
}
try {
const wxidConfigsRaw = cfg.get('wxidConfigs') as Record<string, any> | undefined
if (wxidConfigsRaw && typeof wxidConfigsRaw === 'object') {
const nextConfigs: Record<string, any> = { ...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<string, any> | undefined
if (wxidConfigsRaw && typeof wxidConfigsRaw === 'object') {
const nextConfigs: Record<string, any> = { ...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 })
})
})

View File

@@ -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<boolean> {
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<boolean> {
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 }
}

View File

@@ -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('清理失败,请稍后重试。')

View File

@@ -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) {

View File

@@ -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) {