mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-08-18 20:19:09 +08:00
246 lines
8.0 KiB
TypeScript
246 lines
8.0 KiB
TypeScript
import { useCallback, useEffect, useMemo, useReducer } from 'react'
|
|
import type { Contact } from '../../../../../shared/types'
|
|
import type { SettingsSelfInfo } from '../model/types'
|
|
import { databaseKeyReducer, initialDatabaseKeyState } from './databaseKeyReducer'
|
|
import type { DatabaseKeyController } from './types'
|
|
import { buildDatabaseKeyDiagnostics, isDatabaseKeyFormatValid, mapAutoDetectPhase } from './utils'
|
|
|
|
export function useDatabaseKeyController({
|
|
dbKey,
|
|
dbReady,
|
|
selfInfo,
|
|
onDbKeyChange,
|
|
onDatabaseConnectionChange,
|
|
onSelfInfoChange,
|
|
onContactsChange,
|
|
onFilteredContactsChange,
|
|
onReturnToLogin,
|
|
onNotice
|
|
}: {
|
|
dbKey: string
|
|
dbReady: boolean
|
|
selfInfo: SettingsSelfInfo | null
|
|
onDbKeyChange: (key: string) => void
|
|
onDatabaseConnectionChange: (connected: boolean) => void
|
|
onSelfInfoChange: (info: SettingsSelfInfo | null) => void
|
|
onContactsChange: (contacts: Contact[]) => void
|
|
onFilteredContactsChange: (contacts: Contact[]) => void
|
|
onReturnToLogin: () => void
|
|
onNotice: (message: string) => void
|
|
}): DatabaseKeyController {
|
|
const [state, dispatch] = useReducer(databaseKeyReducer, initialDatabaseKeyState)
|
|
|
|
const refreshEnvironment = useCallback(async (): Promise<void> => {
|
|
const environment = await window.api.getDatabaseKeyEnvironment()
|
|
dispatch({ type: 'ENVIRONMENT_LOADED', environment })
|
|
}, [])
|
|
|
|
const refreshStorage = useCallback(async (): Promise<void> => {
|
|
const result = await window.api.getSavedDbKey()
|
|
dispatch({
|
|
type: 'STORAGE_LOADED',
|
|
saved: result.saved,
|
|
encryptionAvailable: result.encryptionAvailable,
|
|
error: result.success ? undefined : result.error
|
|
})
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
void Promise.all([refreshStorage(), refreshEnvironment()])
|
|
return window.api.onDbKeyStatus(({ message }) => {
|
|
dispatch({ type: 'AUTO_PROGRESS', phase: mapAutoDetectPhase(message) })
|
|
})
|
|
}, [refreshEnvironment, refreshStorage])
|
|
|
|
const editKey = useCallback(
|
|
(value: string): void => {
|
|
onDbKeyChange(value)
|
|
dispatch({ type: 'EDIT' })
|
|
},
|
|
[onDbKeyChange]
|
|
)
|
|
|
|
const pasteKey = useCallback(async (): Promise<void> => {
|
|
const result = await window.api.readDatabaseKeyClipboard()
|
|
if (!result.success || !result.value) {
|
|
onNotice(result.error || '剪贴板中没有可用的数据库密钥')
|
|
return
|
|
}
|
|
editKey(result.value)
|
|
onNotice('已从剪贴板粘贴,请验证后保存')
|
|
}, [editKey, onNotice])
|
|
|
|
const runValidation = useCallback(
|
|
async (key: string): Promise<boolean> => {
|
|
dispatch({ type: 'VALIDATE_START' })
|
|
if (!isDatabaseKeyFormatValid(key)) {
|
|
dispatch({
|
|
type: 'VALIDATE_ERROR',
|
|
at: Date.now(),
|
|
result: { success: false, code: 'INVALID_FORMAT', error: '密钥格式不正确' }
|
|
})
|
|
return false
|
|
}
|
|
const result = await window.api.testConnection(key, selfInfo?.accountRoot)
|
|
if (
|
|
result.success &&
|
|
selfInfo?.wxid &&
|
|
result.wxid &&
|
|
result.wxid.toLowerCase() !== selfInfo.wxid.toLowerCase()
|
|
) {
|
|
result.success = false
|
|
result.code = 'ACCOUNT_MISMATCH'
|
|
result.error = '密钥与当前账号不匹配'
|
|
}
|
|
dispatch({
|
|
type: result.success ? 'VALIDATE_SUCCESS' : 'VALIDATE_ERROR',
|
|
result,
|
|
at: Date.now()
|
|
})
|
|
return result.success
|
|
},
|
|
[selfInfo]
|
|
)
|
|
|
|
const validateKey = useCallback(async (): Promise<void> => {
|
|
await runValidation(dbKey)
|
|
}, [dbKey, runValidation])
|
|
|
|
const saveKey = useCallback(async (): Promise<void> => {
|
|
if (state.status !== 'valid') return
|
|
dispatch({ type: 'SAVE_START' })
|
|
const saved = await window.api.saveDbKey(dbKey)
|
|
if (!saved.success || !saved.key) {
|
|
dispatch({
|
|
type: 'SAVE_ERROR',
|
|
error: saved.encryptionAvailable ? '密钥保存失败' : '系统安全存储不可用'
|
|
})
|
|
return
|
|
}
|
|
const stored = await window.api.getSavedDbKey()
|
|
if (!stored.success || !stored.saved || !stored.key) {
|
|
dispatch({ type: 'SAVE_ERROR', error: '无法确认密钥保存状态' })
|
|
return
|
|
}
|
|
const initialized = await window.api.initDb(stored.key)
|
|
const connected = typeof initialized === 'boolean' ? initialized : initialized.success
|
|
onDbKeyChange(stored.key)
|
|
onDatabaseConnectionChange(connected)
|
|
if (connected) {
|
|
const [self, contacts] = await Promise.all([window.api.getSelf(), window.api.getContacts()])
|
|
onSelfInfoChange(self.ready ? self.info : null)
|
|
onContactsChange(contacts)
|
|
onFilteredContactsChange(contacts)
|
|
} else {
|
|
onSelfInfoChange(null)
|
|
onContactsChange([])
|
|
onFilteredContactsChange([])
|
|
}
|
|
dispatch({ type: 'SAVE_SUCCESS', encryptionAvailable: stored.encryptionAvailable })
|
|
await refreshEnvironment()
|
|
onNotice(connected ? '数据库密钥已安全保存' : '密钥已保存,但数据库重新连接失败')
|
|
}, [
|
|
dbKey,
|
|
onContactsChange,
|
|
onDatabaseConnectionChange,
|
|
onDbKeyChange,
|
|
onFilteredContactsChange,
|
|
onNotice,
|
|
onSelfInfoChange,
|
|
refreshEnvironment,
|
|
state.status
|
|
])
|
|
|
|
const autoDetectKey = useCallback(async (): Promise<void> => {
|
|
dispatch({ type: 'AUTO_START' })
|
|
await refreshEnvironment()
|
|
const result = await window.api.autoGetDbKey({ save: false })
|
|
if (!result.success || !result.key) {
|
|
dispatch({ type: 'AUTO_ERROR', error: result.error || '暂未找到有效密钥' })
|
|
return
|
|
}
|
|
onDbKeyChange(result.key)
|
|
dispatch({ type: 'AUTO_SUCCESS' })
|
|
await runValidation(result.key)
|
|
}, [onDbKeyChange, refreshEnvironment, runValidation])
|
|
|
|
const clearSavedKey = useCallback(async (): Promise<void> => {
|
|
dispatch({ type: 'CLEAR_START' })
|
|
const result = await window.api.clearSavedDbKey()
|
|
if (!result.success) {
|
|
dispatch({ type: 'CLEAR_ERROR', error: '清除密钥失败' })
|
|
return
|
|
}
|
|
await window.api.disconnectDb()
|
|
onDbKeyChange('')
|
|
onDatabaseConnectionChange(false)
|
|
onSelfInfoChange(null)
|
|
onContactsChange([])
|
|
onFilteredContactsChange([])
|
|
dispatch({ type: 'CLEAR_SUCCESS' })
|
|
await refreshEnvironment()
|
|
onNotice('数据库密钥已清除')
|
|
}, [
|
|
onContactsChange,
|
|
onDatabaseConnectionChange,
|
|
onDbKeyChange,
|
|
onFilteredContactsChange,
|
|
onNotice,
|
|
onSelfInfoChange,
|
|
refreshEnvironment
|
|
])
|
|
|
|
const returnToLogin = useCallback(async (): Promise<void> => {
|
|
// macOS WCDB 的 close 会关闭进程级原生运行时,返回登录时只退出 UI 连接态。
|
|
// 用户再次点击连接后由 db:init 复用已验证的本机连接。
|
|
const result = await window.api.disconnectDb({ closeNative: false })
|
|
if (!result.success) {
|
|
onNotice(result.error || '断开数据库连接失败')
|
|
return
|
|
}
|
|
onDatabaseConnectionChange(false)
|
|
onSelfInfoChange(null)
|
|
onContactsChange([])
|
|
onFilteredContactsChange([])
|
|
onReturnToLogin()
|
|
}, [
|
|
onContactsChange,
|
|
onDatabaseConnectionChange,
|
|
onFilteredContactsChange,
|
|
onNotice,
|
|
onReturnToLogin,
|
|
onSelfInfoChange
|
|
])
|
|
|
|
const copyDiagnostics = useCallback(async (): Promise<void> => {
|
|
const result = await window.api.copyText(
|
|
buildDatabaseKeyDiagnostics(state, dbKey, selfInfo, dbReady)
|
|
)
|
|
onNotice(result.success ? '密钥诊断信息已复制' : result.error || '复制诊断信息失败')
|
|
}, [dbKey, dbReady, onNotice, selfInfo, state])
|
|
|
|
const isBusy = ['validating', 'saving', 'clearing', 'auto-detecting'].includes(state.status)
|
|
const canSave = state.status === 'valid' && state.encryptionAvailable
|
|
const pageStatus = useMemo<DatabaseKeyController['pageStatus']>(() => {
|
|
if (state.status === 'validating' || state.status === 'auto-detecting') return 'validating'
|
|
if (state.status === 'invalid') return 'invalid'
|
|
return state.saved ? 'saved' : 'unconfigured'
|
|
}, [state.saved, state.status])
|
|
|
|
return {
|
|
state,
|
|
isBusy,
|
|
canSave,
|
|
pageStatus,
|
|
editKey,
|
|
pasteKey,
|
|
validateKey,
|
|
saveKey,
|
|
autoDetectKey,
|
|
clearSavedKey,
|
|
returnToLogin,
|
|
copyDiagnostics,
|
|
refreshEnvironment
|
|
}
|
|
}
|