mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-10-08 15:43:22 +08:00
fix: expose macOS login-time key detection
This commit is contained in:
@@ -137,6 +137,7 @@ export function DatabaseConnectionPage({
|
|||||||
}: DatabaseConnectionPageProps): React.ReactElement {
|
}: DatabaseConnectionPageProps): React.ReactElement {
|
||||||
const isMac = platform === 'darwin'
|
const isMac = platform === 'darwin'
|
||||||
const isIntelMac = isMac && environment?.architecture === 'x64'
|
const isIntelMac = isMac && environment?.architecture === 'x64'
|
||||||
|
const isAppleSiliconMac = isMac && !isIntelMac
|
||||||
const defaultPath = isMac
|
const defaultPath = isMac
|
||||||
? '~/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/'
|
? '~/Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/'
|
||||||
: 'C:\\Users\\...\\WeChat Files\\Msg'
|
: 'C:\\Users\\...\\WeChat Files\\Msg'
|
||||||
@@ -189,18 +190,16 @@ export function DatabaseConnectionPage({
|
|||||||
<li>
|
<li>
|
||||||
<span>2</span>
|
<span>2</span>
|
||||||
<div>
|
<div>
|
||||||
<strong>{isIntelMac ? '让微信停在登录页面' : '准备连接组件'}</strong>
|
<strong>{isMac ? '让微信停在登录页面' : '准备连接组件'}</strong>
|
||||||
<small>
|
<small>
|
||||||
{isIntelMac
|
{isMac ? '启动微信,但暂时不要点击登录' : '页面会按当前系统给出对应步骤'}
|
||||||
? '启动微信,但暂时不要点击登录'
|
|
||||||
: '页面会按当前系统给出对应步骤'}
|
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<span>3</span>
|
<span>3</span>
|
||||||
<div>
|
<div>
|
||||||
<strong>{isIntelMac ? '获取密钥并验证连接' : '登录并验证连接'}</strong>
|
<strong>{isMac ? '获取密钥并验证连接' : '登录并验证连接'}</strong>
|
||||||
<small>验证通过后进入主界面</small>
|
<small>验证通过后进入主界面</small>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -248,11 +247,13 @@ export function DatabaseConnectionPage({
|
|||||||
: [
|
: [
|
||||||
'检查本机环境',
|
'检查本机环境',
|
||||||
'让微信停在登录页面',
|
'让微信停在登录页面',
|
||||||
isIntelMac ? '确认开始获取密钥' : '确认开始准备',
|
isMac ? '确认开始获取密钥' : '确认开始准备',
|
||||||
isIntelMac
|
isIntelMac
|
||||||
? '正在获取 Intel Mac 密钥'
|
? '正在获取 Intel Mac 密钥'
|
||||||
: `正在完成 ${isMac ? 'macOS' : 'Windows'} 授权`,
|
: isAppleSiliconMac
|
||||||
isIntelMac
|
? '正在监听 macOS 登录密钥'
|
||||||
|
: '正在完成 Windows 授权',
|
||||||
|
isMac
|
||||||
? '获取成功,验证数据库连接'
|
? '获取成功,验证数据库连接'
|
||||||
: '获取成功, 现在可以重新登录微信了',
|
: '获取成功, 现在可以重新登录微信了',
|
||||||
'验证数据库连接'
|
'验证数据库连接'
|
||||||
@@ -264,14 +265,14 @@ export function DatabaseConnectionPage({
|
|||||||
: status ||
|
: status ||
|
||||||
[
|
[
|
||||||
'确认下方检测结果;没有找到目录时可以手动选择。',
|
'确认下方检测结果;没有找到目录时可以手动选择。',
|
||||||
isIntelMac
|
isMac
|
||||||
? '请启动微信并停留在未登录界面,暂时不要点击登录,然后点击“我已准备好”。'
|
? '请启动微信并停留在未登录界面,暂时不要点击登录,然后点击“我已准备好”。'
|
||||||
: '请退出当前微信账号,让微信停留在登录页面,然后点击“我已准备好”。',
|
: '请退出当前微信账号,让微信停留在登录页面,然后点击“我已准备好”。',
|
||||||
isIntelMac ? '确认开始获取密钥。' : '开始后请按页面提示完成系统授权。',
|
isMac ? '确认开始获取密钥。' : '开始后请按页面提示完成系统授权。',
|
||||||
isIntelMac
|
isMac
|
||||||
? '等页面提示可以登录后,立即回到微信点击登录。'
|
? '等页面提示可以登录后,立即回到微信点击登录。'
|
||||||
: '正在准备连接组件,请不要关闭微信或 TraceMemo。',
|
: '正在准备连接组件,请不要关闭微信或 TraceMemo。',
|
||||||
isIntelMac
|
isMac
|
||||||
? '密钥已获取,请继续验证数据库。'
|
? '密钥已获取,请继续验证数据库。'
|
||||||
: '请回到微信完成登录,登录成功后再回来验证。',
|
: '请回到微信完成登录,登录成功后再回来验证。',
|
||||||
'正在验证密钥和本地数据库,请稍候。'
|
'正在验证密钥和本地数据库,请稍候。'
|
||||||
@@ -445,12 +446,12 @@ export function DatabaseConnectionPage({
|
|||||||
)}
|
)}
|
||||||
{guideStep === 3 && (
|
{guideStep === 3 && (
|
||||||
<Button className="database-login-primary" onClick={onAutoGetKey}>
|
<Button className="database-login-primary" onClick={onAutoGetKey}>
|
||||||
{isIntelMac ? '开始获取密钥' : '开始准备连接组件'}
|
{isMac ? '开始获取密钥' : '开始准备连接组件'}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{guideStep === 4 && (
|
{guideStep === 4 && (
|
||||||
<Button className="database-login-primary" disabled>
|
<Button className="database-login-primary" disabled>
|
||||||
正在准备连接组件…
|
{isMac ? '正在获取密钥…' : '正在准备连接组件…'}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{guideStep === 5 && (
|
{guideStep === 5 && (
|
||||||
@@ -491,7 +492,7 @@ export function DatabaseConnectionPage({
|
|||||||
<>
|
<>
|
||||||
{isIntelMac
|
{isIntelMac
|
||||||
? 'Intel Mac 首次使用需要先准备连接环境,按页面提示完成即可。'
|
? 'Intel Mac 首次使用需要先准备连接环境,按页面提示完成即可。'
|
||||||
: 'macOS 首次获取密钥需要关闭 SIP。'}{' '}
|
: 'macOS Apple 芯片首次获取密钥需要关闭 SIP,并在监听期间点击微信登录。'}{' '}
|
||||||
<a href={isIntelMac ? GUIDE_URL : macKeyFaqUrl} target="_blank" rel="noreferrer">
|
<a href={isIntelMac ? GUIDE_URL : macKeyFaqUrl} target="_blank" rel="noreferrer">
|
||||||
查看说明
|
查看说明
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -2,7 +2,14 @@ import type { DatabaseKeyState } from './types'
|
|||||||
import { runtimePlatform } from '../../../utils/runtime-environment'
|
import { runtimePlatform } from '../../../utils/runtime-environment'
|
||||||
import { Button } from '../../../components/ui'
|
import { Button } from '../../../components/ui'
|
||||||
|
|
||||||
const PHASES = ['查找微信进程', '识别微信版本', '扫描候选密钥', '验证数据库', '获取完成']
|
const DEFAULT_PHASES = ['查找微信进程', '识别微信版本', '扫描候选密钥', '验证数据库', '获取完成']
|
||||||
|
const MAC_LOGIN_PHASES = [
|
||||||
|
'查找微信进程',
|
||||||
|
'等待管理员授权',
|
||||||
|
'监听登录密钥',
|
||||||
|
'验证数据库',
|
||||||
|
'获取完成'
|
||||||
|
]
|
||||||
|
|
||||||
export function DatabaseKeyAutoDetect({
|
export function DatabaseKeyAutoDetect({
|
||||||
state,
|
state,
|
||||||
@@ -17,8 +24,12 @@ export function DatabaseKeyAutoDetect({
|
|||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
const environment = state.environment
|
const environment = state.environment
|
||||||
const platform = environment?.platform || runtimePlatform
|
const platform = environment?.platform || runtimePlatform
|
||||||
|
const isMac = platform === 'darwin'
|
||||||
const isIntelMac = platform === 'darwin' && environment?.architecture === 'x64'
|
const isIntelMac = platform === 'darwin' && environment?.architecture === 'x64'
|
||||||
if (platform !== 'win32' && !isIntelMac) {
|
const isAppleSiliconMac = isMac && !isIntelMac
|
||||||
|
const supported = environment?.autoDetectSupported ?? (platform === 'win32' || isMac)
|
||||||
|
const phases = isAppleSiliconMac ? MAC_LOGIN_PHASES : DEFAULT_PHASES
|
||||||
|
if (!supported) {
|
||||||
return (
|
return (
|
||||||
<section className="settings-card database-key-auto database-key-auto-manual">
|
<section className="settings-card database-key-auto database-key-auto-manual">
|
||||||
<div>
|
<div>
|
||||||
@@ -32,10 +43,14 @@ export function DatabaseKeyAutoDetect({
|
|||||||
<section className="settings-card database-key-auto">
|
<section className="settings-card database-key-auto">
|
||||||
<div className="database-key-auto-heading">
|
<div className="database-key-auto-heading">
|
||||||
<div>
|
<div>
|
||||||
<strong>{isIntelMac ? 'Intel Mac 自动获取' : 'Windows 自动获取'}</strong>
|
<strong>
|
||||||
|
{isIntelMac ? 'Intel Mac 自动获取' : isMac ? 'macOS 自动获取' : 'Windows 自动获取'}
|
||||||
|
</strong>
|
||||||
<p>
|
<p>
|
||||||
{isIntelMac
|
{isIntelMac
|
||||||
? '请让微信停在未登录界面,按页面提示操作即可。'
|
? '请让微信停在未登录界面,按页面提示操作即可。'
|
||||||
|
: isAppleSiliconMac
|
||||||
|
? '执行时会请求管理员授权;授权后在微信登录界面点击“登录”即可,已有登录凭据时通常不需要扫码。监听最长两分钟,结束后会明确显示结果。'
|
||||||
: 'TraceMemo 可在微信桌面端正在运行时,通过本机内存扫描尝试获取数据库密钥。'}
|
: 'TraceMemo 可在微信桌面端正在运行时,通过本机内存扫描尝试获取数据库密钥。'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,9 +65,7 @@ export function DatabaseKeyAutoDetect({
|
|||||||
<li className={environment?.accountIdentified ? 'ok' : ''}>
|
<li className={environment?.accountIdentified ? 'ok' : ''}>
|
||||||
当前账号:{environment?.accountIdentified ? '已识别' : '尚未识别'}
|
当前账号:{environment?.accountIdentified ? '已识别' : '尚未识别'}
|
||||||
</li>
|
</li>
|
||||||
<li className={platform === 'win32' || isIntelMac ? 'ok' : ''}>
|
<li className={supported ? 'ok' : ''}>当前平台:{supported ? '支持' : '不支持'}</li>
|
||||||
当前平台:{platform === 'win32' || isIntelMac ? '支持' : '不支持'}
|
|
||||||
</li>
|
|
||||||
{isIntelMac && (
|
{isIntelMac && (
|
||||||
<>
|
<>
|
||||||
<li className={environment?.pythonAvailable && environment?.fridaAvailable ? 'ok' : ''}>
|
<li className={environment?.pythonAvailable && environment?.fridaAvailable ? 'ok' : ''}>
|
||||||
@@ -66,7 +79,7 @@ export function DatabaseKeyAutoDetect({
|
|||||||
</ul>
|
</ul>
|
||||||
{state.status === 'auto-detecting' && (
|
{state.status === 'auto-detecting' && (
|
||||||
<ol className="database-key-phases">
|
<ol className="database-key-phases">
|
||||||
{PHASES.map((phase, index) => (
|
{phases.map((phase, index) => (
|
||||||
<li key={phase} className={state.autoPhase >= index + 1 ? 'active' : ''}>
|
<li key={phase} className={state.autoPhase >= index + 1 ? 'active' : ''}>
|
||||||
{phase}
|
{phase}
|
||||||
</li>
|
</li>
|
||||||
@@ -80,6 +93,8 @@ export function DatabaseKeyAutoDetect({
|
|||||||
<p>
|
<p>
|
||||||
{isIntelMac
|
{isIntelMac
|
||||||
? '请让微信回到未登录界面,重新开始后按页面提示操作。'
|
? '请让微信回到未登录界面,重新开始后按页面提示操作。'
|
||||||
|
: isAppleSiliconMac
|
||||||
|
? '请先让微信停留在登录界面,点击自动获取并完成管理员授权,然后点击微信“登录”。'
|
||||||
: '请保持微信正在运行,登录目标账号并打开几个聊天窗口后重试。'}
|
: '请保持微信正在运行,登录目标账号并打开几个聊天窗口后重试。'}
|
||||||
</p>
|
</p>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -155,6 +155,7 @@ export function useDatabaseKeyController({
|
|||||||
|
|
||||||
const autoDetectKey = useCallback(async (): Promise<void> => {
|
const autoDetectKey = useCallback(async (): Promise<void> => {
|
||||||
dispatch({ type: 'AUTO_START' })
|
dispatch({ type: 'AUTO_START' })
|
||||||
|
try {
|
||||||
await refreshEnvironment()
|
await refreshEnvironment()
|
||||||
const result = await window.api.autoGetDbKey(selfInfo?.accountRoot || '', { save: false })
|
const result = await window.api.autoGetDbKey(selfInfo?.accountRoot || '', { save: false })
|
||||||
if (!result.success || !result.key) {
|
if (!result.success || !result.key) {
|
||||||
@@ -164,6 +165,12 @@ export function useDatabaseKeyController({
|
|||||||
onDbKeyChange(result.key)
|
onDbKeyChange(result.key)
|
||||||
dispatch({ type: 'AUTO_SUCCESS' })
|
dispatch({ type: 'AUTO_SUCCESS' })
|
||||||
await runValidation(result.key)
|
await runValidation(result.key)
|
||||||
|
} catch {
|
||||||
|
dispatch({
|
||||||
|
type: 'AUTO_ERROR',
|
||||||
|
error: '自动获取密钥未完成,请确认微信仍在运行后重试。'
|
||||||
|
})
|
||||||
|
}
|
||||||
}, [onDbKeyChange, refreshEnvironment, runValidation, selfInfo?.accountRoot])
|
}, [onDbKeyChange, refreshEnvironment, runValidation, selfInfo?.accountRoot])
|
||||||
|
|
||||||
const clearSavedKey = useCallback(async (): Promise<void> => {
|
const clearSavedKey = useCallback(async (): Promise<void> => {
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ export const isDatabaseKeyFormatValid = (value: string): boolean =>
|
|||||||
export function mapAutoDetectPhase(message: string): number {
|
export function mapAutoDetectPhase(message: string): number {
|
||||||
if (/完成|成功|已获取/.test(message)) return 5
|
if (/完成|成功|已获取/.test(message)) return 5
|
||||||
if (/验证|校验/.test(message)) return 4
|
if (/验证|校验/.test(message)) return 4
|
||||||
if (/扫描|候选|获取/.test(message)) return 3
|
if (/登录|监听|扫描|候选|获取/.test(message)) return 3
|
||||||
if (/版本|组件|窗口/.test(message)) return 2
|
if (/授权|版本|组件|窗口/.test(message)) return 2
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,43 @@ function renderPage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('DatabaseConnectionPage', () => {
|
describe('DatabaseConnectionPage', () => {
|
||||||
|
it('guides Apple Silicon users to log in while key capture is active', async () => {
|
||||||
|
const onAutoGetKey = vi.fn()
|
||||||
|
const { props, rerender } = renderPage({
|
||||||
|
platform: 'darwin',
|
||||||
|
mode: 'automatic',
|
||||||
|
guideStep: 3,
|
||||||
|
onAutoGetKey,
|
||||||
|
environment: {
|
||||||
|
platform: 'darwin',
|
||||||
|
architecture: 'arm64',
|
||||||
|
osVersion: 'macOS fixture',
|
||||||
|
appVersion: 'v2.3.0',
|
||||||
|
wechatVersion: '4.1.13',
|
||||||
|
dataStructureVersion: '微信 4.x(WCDB)',
|
||||||
|
dataDirectoryDetected: true,
|
||||||
|
diagnosticSummary: 'fixture',
|
||||||
|
autoDetectSupported: true,
|
||||||
|
wechatRunning: true,
|
||||||
|
accountIdentified: true,
|
||||||
|
dbConnected: false,
|
||||||
|
encryptionAvailable: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
await userEvent.click(screen.getByRole('button', { name: '开始获取密钥' }))
|
||||||
|
expect(onAutoGetKey).toHaveBeenCalledOnce()
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<TooltipProvider>
|
||||||
|
<DatabaseConnectionPage {...props} mode="automatic" guideStep={4} isFetching />
|
||||||
|
</TooltipProvider>
|
||||||
|
)
|
||||||
|
expect(screen.getByText('正在监听 macOS 登录密钥')).toBeVisible()
|
||||||
|
expect(screen.getByText(/立即回到微信点击登录/)).toBeVisible()
|
||||||
|
expect(screen.getByRole('button', { name: '正在获取密钥…' })).toBeDisabled()
|
||||||
|
})
|
||||||
|
|
||||||
it('uses the automatic Intel Mac flow without exposing implementation details', () => {
|
it('uses the automatic Intel Mac flow without exposing implementation details', () => {
|
||||||
renderPage({
|
renderPage({
|
||||||
platform: 'darwin',
|
platform: 'darwin',
|
||||||
|
|||||||
@@ -31,6 +31,34 @@ const state: DatabaseKeyState = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('database key controls', () => {
|
describe('database key controls', () => {
|
||||||
|
it('offers login-time key capture on Apple Silicon macOS', async () => {
|
||||||
|
const user = userEvent.setup()
|
||||||
|
const onDetect = vi.fn()
|
||||||
|
render(
|
||||||
|
<DatabaseKeyAutoDetect
|
||||||
|
state={{
|
||||||
|
...state,
|
||||||
|
environment: {
|
||||||
|
...state.environment!,
|
||||||
|
platform: 'darwin',
|
||||||
|
architecture: 'arm64',
|
||||||
|
osVersion: 'macOS fixture',
|
||||||
|
wechatVersion: '4.1.13',
|
||||||
|
autoDetectSupported: true
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={false}
|
||||||
|
onDetect={onDetect}
|
||||||
|
onRefresh={vi.fn()}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(screen.getByText('macOS 自动获取')).toBeVisible()
|
||||||
|
expect(screen.getByText(/通常不需要扫码/)).toBeVisible()
|
||||||
|
await user.click(screen.getByRole('button', { name: '自动获取密钥' }))
|
||||||
|
expect(onDetect).toHaveBeenCalledOnce()
|
||||||
|
})
|
||||||
|
|
||||||
it('offers automatic key detection on Intel Mac without exposing implementation details', async () => {
|
it('offers automatic key detection on Intel Mac without exposing implementation details', async () => {
|
||||||
const user = userEvent.setup()
|
const user = userEvent.setup()
|
||||||
const onDetect = vi.fn()
|
const onDetect = vi.fn()
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ describe('database key validation', () => {
|
|||||||
|
|
||||||
it('maps automatic detection progress into stable phases', () => {
|
it('maps automatic detection progress into stable phases', () => {
|
||||||
expect(mapAutoDetectPhase('正在查找微信进程')).toBeGreaterThan(0)
|
expect(mapAutoDetectPhase('正在查找微信进程')).toBeGreaterThan(0)
|
||||||
|
expect(mapAutoDetectPhase('正在请求管理员授权')).toBe(2)
|
||||||
|
expect(mapAutoDetectPhase('授权后请在微信登录界面点击“登录”')).toBe(3)
|
||||||
expect(mapAutoDetectPhase('已获取数据库密钥')).toBe(5)
|
expect(mapAutoDetectPhase('已获取数据库密钥')).toBe(5)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user