mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-08-17 11:37:06 +08:00
fix: 修复登录报错key问题
This commit is contained in:
+40
-38
@@ -5,6 +5,7 @@ import path from 'path'
|
|||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
import { isValidDatabaseKey } from './database-key-store'
|
import { isValidDatabaseKey } from './database-key-store'
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
|
import { findResource, getResourceCandidates } from './resource-paths'
|
||||||
|
|
||||||
const execFileAsync = promisify(execFile)
|
const execFileAsync = promisify(execFile)
|
||||||
|
|
||||||
@@ -25,33 +26,10 @@ export interface ImageKeyResult {
|
|||||||
|
|
||||||
export class KeyServiceMac {
|
export class KeyServiceMac {
|
||||||
private getHelperPath(): string {
|
private getHelperPath(): string {
|
||||||
// 多 candidate fallback:覆盖 extraResources、asarUnpack、dev 三种场景
|
const helperPath = findResource('xkey_helper')
|
||||||
// (extraResources → Contents/Resources/resources/;asarUnpack 同路径;dev → cwd 或 app.getAppPath)
|
|
||||||
const candidates = [
|
|
||||||
// 1) extraResources 标准位置(electron-builder.yml 配的就是这个)
|
|
||||||
path.join(process.resourcesPath, 'resources', 'xkey_helper'),
|
|
||||||
// 2) process.resourcesPath 直接(防止 extraResources 没复制成功)
|
|
||||||
path.join(process.resourcesPath, 'xkey_helper'),
|
|
||||||
// 3) asarUnpack 路径(如果在 asar 内的 resources/ 被解包到 app.asar.unpacked)
|
|
||||||
path.join(app.getAppPath(), 'app.asar.unpacked', 'resources', 'xkey_helper'),
|
|
||||||
// 4) dev 模式 + 打包后某些版本 app.getAppPath() 也指向 .app 根目录
|
|
||||||
path.join(app.getAppPath(), 'resources', 'xkey_helper'),
|
|
||||||
// 5) dev 模式:cwd
|
|
||||||
path.join(process.cwd(), 'resources', 'xkey_helper')
|
|
||||||
].filter((p, idx, arr) => arr.indexOf(p) === idx) // 去重
|
|
||||||
|
|
||||||
// 诊断:即使命中也打 log,方便排查"装了但找不到"的问题(translocation / quarantine)
|
|
||||||
const statusList = candidates.map((candidate) => ({
|
|
||||||
path: candidate,
|
|
||||||
exists: fs.existsSync(candidate)
|
|
||||||
}))
|
|
||||||
console.log('[KeyServiceMac] xkey_helper candidates:', JSON.stringify(statusList))
|
|
||||||
const helperPath = candidates.find((candidate) => fs.existsSync(candidate))
|
|
||||||
if (!helperPath) {
|
if (!helperPath) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`找不到 xkey_helper(尝试 ${candidates.length} 个路径;` +
|
`找不到 xkey_helper(已检查:${getResourceCandidates('xkey_helper').join(';')})`
|
||||||
` app.isPackaged=${app.isPackaged} resourcesPath=${process.resourcesPath} ` +
|
|
||||||
` appPath=${app.getAppPath()} cwd=${process.cwd()})`
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return helperPath
|
return helperPath
|
||||||
@@ -163,7 +141,7 @@ export class KeyServiceMac {
|
|||||||
'return "ERR::" & errNum & "::" & errMsg',
|
'return "ERR::" & errNum & "::" & errMsg',
|
||||||
'end try'
|
'end try'
|
||||||
]
|
]
|
||||||
onStatus?.('授权后请保持微信已登录并活动...')
|
onStatus?.('授权后请保持微信登录界面 并点击登录微信...')
|
||||||
const { stdout } = await execFileAsync(
|
const { stdout } = await execFileAsync(
|
||||||
'/usr/bin/osascript',
|
'/usr/bin/osascript',
|
||||||
scriptLines.flatMap((line) => ['-e', line]),
|
scriptLines.flatMap((line) => ['-e', line]),
|
||||||
@@ -207,7 +185,8 @@ export class KeyServiceMac {
|
|||||||
|
|
||||||
const orderedWxids: string[] = []
|
const orderedWxids: string[] = []
|
||||||
this.pushAccountIdCandidates(orderedWxids, path.basename(candidateAccountPath))
|
this.pushAccountIdCandidates(orderedWxids, path.basename(candidateAccountPath))
|
||||||
for (const candidate of wxidCandidates) this.pushAccountIdCandidates(orderedWxids, candidate)
|
for (const candidate of wxidCandidates)
|
||||||
|
this.pushAccountIdCandidates(orderedWxids, candidate)
|
||||||
|
|
||||||
onStatus?.(`正在校验候选 wxid(${orderedWxids.length} 个)...`)
|
onStatus?.(`正在校验候选 wxid(${orderedWxids.length} 个)...`)
|
||||||
for (const candidateWxid of orderedWxids) {
|
for (const candidateWxid of orderedWxids) {
|
||||||
@@ -255,20 +234,33 @@ export class KeyServiceMac {
|
|||||||
private getKvcommCandidates(accountPath?: string): string[] {
|
private getKvcommCandidates(accountPath?: string): string[] {
|
||||||
const home = app.getPath('home')
|
const home = app.getPath('home')
|
||||||
const candidates = new Set<string>([
|
const candidates = new Set<string>([
|
||||||
path.join(home, 'Library/Containers/com.tencent.xinWeChat/Data/Documents/app_data/net/kvcomm'),
|
path.join(
|
||||||
path.join(home, 'Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/xwechat/net/kvcomm'),
|
home,
|
||||||
path.join(home, 'Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/net/kvcomm'),
|
'Library/Containers/com.tencent.xinWeChat/Data/Documents/app_data/net/kvcomm'
|
||||||
|
),
|
||||||
|
path.join(
|
||||||
|
home,
|
||||||
|
'Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/xwechat/net/kvcomm'
|
||||||
|
),
|
||||||
|
path.join(
|
||||||
|
home,
|
||||||
|
'Library/Containers/com.tencent.xinWeChat/Data/Library/Application Support/com.tencent.xinWeChat/net/kvcomm'
|
||||||
|
),
|
||||||
path.join(home, 'Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat/net/kvcomm')
|
path.join(home, 'Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat/net/kvcomm')
|
||||||
])
|
])
|
||||||
|
|
||||||
const normalized = String(accountPath || '').replace(/\\/g, '/').replace(/\/+$/, '')
|
const normalized = String(accountPath || '')
|
||||||
|
.replace(/\\/g, '/')
|
||||||
|
.replace(/\/+$/, '')
|
||||||
const marker = '/xwechat_files'
|
const marker = '/xwechat_files'
|
||||||
const markerIndex = normalized.indexOf(marker)
|
const markerIndex = normalized.indexOf(marker)
|
||||||
if (markerIndex >= 0) {
|
if (markerIndex >= 0) {
|
||||||
candidates.add(`${normalized.slice(0, markerIndex)}/app_data/net/kvcomm`)
|
candidates.add(`${normalized.slice(0, markerIndex)}/app_data/net/kvcomm`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const newPathMatch = normalized.match(/^(.*\/com\.tencent\.xinWeChat\/(?:\d+\.\d+b\d+\.\d+|\d+\.\d+\.\d+))/)
|
const newPathMatch = normalized.match(
|
||||||
|
/^(.*\/com\.tencent\.xinWeChat\/(?:\d+\.\d+b\d+\.\d+|\d+\.\d+\.\d+))/
|
||||||
|
)
|
||||||
if (newPathMatch) {
|
if (newPathMatch) {
|
||||||
candidates.add(`${newPathMatch[1]}/net/kvcomm`)
|
candidates.add(`${newPathMatch[1]}/net/kvcomm`)
|
||||||
candidates.add(`${newPathMatch[1]}/xwechat/net/kvcomm`)
|
candidates.add(`${newPathMatch[1]}/xwechat/net/kvcomm`)
|
||||||
@@ -281,7 +273,9 @@ export class KeyServiceMac {
|
|||||||
const candidates: string[] = []
|
const candidates: string[] = []
|
||||||
this.pushAccountIdCandidates(candidates, wxidParam)
|
this.pushAccountIdCandidates(candidates, wxidParam)
|
||||||
|
|
||||||
const normalized = String(accountPath || '').replace(/\\/g, '/').replace(/\/+$/, '')
|
const normalized = String(accountPath || '')
|
||||||
|
.replace(/\\/g, '/')
|
||||||
|
.replace(/\/+$/, '')
|
||||||
if (normalized) {
|
if (normalized) {
|
||||||
this.pushAccountIdCandidates(candidates, path.basename(normalized))
|
this.pushAccountIdCandidates(candidates, path.basename(normalized))
|
||||||
const root = this.resolveXwechatRootFromPath(normalized)
|
const root = this.resolveXwechatRootFromPath(normalized)
|
||||||
@@ -290,7 +284,8 @@ export class KeyServiceMac {
|
|||||||
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
||||||
if (!entry.isDirectory()) continue
|
if (!entry.isDirectory()) continue
|
||||||
const entryPath = path.join(root, entry.name)
|
const entryPath = path.join(root, entry.name)
|
||||||
if (this.isAccountDirPath(entryPath)) this.pushAccountIdCandidates(candidates, entry.name)
|
if (this.isAccountDirPath(entryPath))
|
||||||
|
this.pushAccountIdCandidates(candidates, entry.name)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore unreadable directories.
|
// Ignore unreadable directories.
|
||||||
@@ -316,7 +311,8 @@ export class KeyServiceMac {
|
|||||||
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
||||||
if (!entry.isDirectory()) continue
|
if (!entry.isDirectory()) continue
|
||||||
const entryPath = path.join(root, entry.name)
|
const entryPath = path.join(root, entry.name)
|
||||||
if (this.isAccountDirPath(entryPath) && this.isReasonableAccountId(entry.name)) push(entryPath)
|
if (this.isAccountDirPath(entryPath) && this.isReasonableAccountId(entry.name))
|
||||||
|
push(entryPath)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore unreadable directories.
|
// Ignore unreadable directories.
|
||||||
@@ -326,12 +322,16 @@ export class KeyServiceMac {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private resolveXwechatRootFromPath(accountPath?: string): string | null {
|
private resolveXwechatRootFromPath(accountPath?: string): string | null {
|
||||||
const normalized = String(accountPath || '').replace(/\\/g, '/').replace(/\/+$/, '')
|
const normalized = String(accountPath || '')
|
||||||
|
.replace(/\\/g, '/')
|
||||||
|
.replace(/\/+$/, '')
|
||||||
if (!normalized) return null
|
if (!normalized) return null
|
||||||
const marker = '/xwechat_files'
|
const marker = '/xwechat_files'
|
||||||
const markerIndex = normalized.indexOf(marker)
|
const markerIndex = normalized.indexOf(marker)
|
||||||
if (markerIndex >= 0) return normalized.slice(0, markerIndex + marker.length)
|
if (markerIndex >= 0) return normalized.slice(0, markerIndex + marker.length)
|
||||||
const newPathMatch = normalized.match(/^(.*\/com\.tencent\.xinWeChat\/(?:\d+\.\d+b\d+\.\d+|\d+\.\d+\.\d+))(\/|$)/)
|
const newPathMatch = normalized.match(
|
||||||
|
/^(.*\/com\.tencent\.xinWeChat\/(?:\d+\.\d+b\d+\.\d+|\d+\.\d+\.\d+))(\/|$)/
|
||||||
|
)
|
||||||
return newPathMatch ? newPathMatch[1] : null
|
return newPathMatch ? newPathMatch[1] : null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,7 +366,9 @@ export class KeyServiceMac {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private isReasonableAccountId(value: string): boolean {
|
private isReasonableAccountId(value: string): boolean {
|
||||||
const lowered = String(value || '').trim().toLowerCase()
|
const lowered = String(value || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
if (!lowered || lowered.includes('/') || lowered.includes('\\')) return false
|
if (!lowered || lowered.includes('/') || lowered.includes('\\')) return false
|
||||||
return !['xwechat_files', 'all_users', 'backup', 'wmpf', 'app_data'].includes(lowered)
|
return !['xwechat_files', 'all_users', 'backup', 'wmpf', 'app_data'].includes(lowered)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
// Windows native bridge adapted from WeFlow. Koffi Win32 callbacks and optional
|
// Windows native bridge adapted from WeFlow. Koffi Win32 callbacks and optional
|
||||||
// helpers are intentionally dynamic; keep this file isolated from strict TS.
|
// helpers are intentionally dynamic; keep this file isolated from strict TS.
|
||||||
import { app } from 'electron'
|
|
||||||
import { join, dirname, delimiter } from 'path'
|
import { join, dirname, delimiter } from 'path'
|
||||||
import { existsSync, copyFileSync, mkdirSync } from 'fs'
|
import { existsSync, copyFileSync, mkdirSync } from 'fs'
|
||||||
import { execFile, spawn } from 'child_process'
|
import { execFile, spawn } from 'child_process'
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
|
import { getResourceRoots as getSharedResourceRoots } from './resource-paths'
|
||||||
|
|
||||||
const execFileAsync = promisify(execFile)
|
const execFileAsync = promisify(execFile)
|
||||||
|
|
||||||
@@ -69,14 +69,7 @@ export class KeyService {
|
|||||||
private readonly DB_KEY_PROCESS_CHECK_INTERVAL_MS = 1000
|
private readonly DB_KEY_PROCESS_CHECK_INTERVAL_MS = 1000
|
||||||
|
|
||||||
private getResourceRoots(): string[] {
|
private getResourceRoots(): string[] {
|
||||||
const roots = [
|
return getSharedResourceRoots()
|
||||||
join(process.cwd(), 'resources'),
|
|
||||||
join(process.cwd(), 'resources', 'resources'),
|
|
||||||
join(process.resourcesPath || '', 'resources'),
|
|
||||||
process.resourcesPath || '',
|
|
||||||
join(app.getAppPath(), 'resources')
|
|
||||||
]
|
|
||||||
return Array.from(new Set(roots.filter((root) => root && existsSync(root))))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDllPath(): string {
|
private getDllPath(): string {
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { app } from 'electron'
|
||||||
|
import { existsSync } from 'fs'
|
||||||
|
import { dirname, join } from 'path'
|
||||||
|
|
||||||
|
function unique(values: string[]): string[] {
|
||||||
|
return Array.from(new Set(values.filter(Boolean)))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getResourceRoots(): string[] {
|
||||||
|
const appPath = app.getAppPath()
|
||||||
|
const appPathDir = dirname(appPath)
|
||||||
|
const execDir = dirname(process.execPath)
|
||||||
|
|
||||||
|
return unique([
|
||||||
|
process.env.WECHATEXPLORER_RESOURCES_PATH || '',
|
||||||
|
join(process.cwd(), 'resources'),
|
||||||
|
join(process.cwd(), 'resources', 'resources'),
|
||||||
|
join(process.resourcesPath || '', 'resources'),
|
||||||
|
process.resourcesPath || '',
|
||||||
|
join(appPath, 'resources'),
|
||||||
|
join(appPathDir, 'resources'),
|
||||||
|
appPathDir,
|
||||||
|
join(execDir, 'resources'),
|
||||||
|
join(dirname(execDir), 'Resources', 'resources'),
|
||||||
|
join(dirname(execDir), 'Resources')
|
||||||
|
]).filter((root) => existsSync(root))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getResourceCandidates(relativePath: string): string[] {
|
||||||
|
return unique(getResourceRoots().map((root) => join(root, relativePath)))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findResource(relativePath: string): string | null {
|
||||||
|
return getResourceCandidates(relativePath).find((candidate) => existsSync(candidate)) || null
|
||||||
|
}
|
||||||
+35
-28
@@ -4,6 +4,7 @@ import os from 'os'
|
|||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
import { createRequire } from 'module'
|
import { createRequire } from 'module'
|
||||||
import { createConnection, Socket } from 'net'
|
import { createConnection, Socket } from 'net'
|
||||||
|
import { getResourceRoots } from './resource-paths'
|
||||||
|
|
||||||
export interface Wcdb4Session {
|
export interface Wcdb4Session {
|
||||||
username: string
|
username: string
|
||||||
@@ -81,14 +82,14 @@ export function bootstrapWcdbNative(libPath?: string, libDirOverride?: string):
|
|||||||
resourcePath: string
|
resourcePath: string
|
||||||
) => number
|
) => number
|
||||||
const resourceRoots = Array.from(
|
const resourceRoots = Array.from(
|
||||||
new Set([
|
new Set(
|
||||||
libDir,
|
[
|
||||||
path.dirname(libDir),
|
libDir,
|
||||||
process.env.WCDB_RESOURCES_PATH || '',
|
path.dirname(libDir),
|
||||||
path.join(process.resourcesPath || process.cwd(), 'resources'),
|
process.env.WCDB_RESOURCES_PATH || '',
|
||||||
process.resourcesPath || process.cwd(),
|
...getResourceRoots()
|
||||||
path.join(process.cwd(), 'resources')
|
].filter(Boolean)
|
||||||
])
|
)
|
||||||
)
|
)
|
||||||
let lastCode = -1
|
let lastCode = -1
|
||||||
let initOk = false
|
let initOk = false
|
||||||
@@ -1330,14 +1331,14 @@ export class Wcdb4Client {
|
|||||||
) => number
|
) => number
|
||||||
|
|
||||||
const resourceRoots = Array.from(
|
const resourceRoots = Array.from(
|
||||||
new Set([
|
new Set(
|
||||||
libDir,
|
[
|
||||||
path.dirname(libDir),
|
libDir,
|
||||||
process.env.WCDB_RESOURCES_PATH || '',
|
path.dirname(libDir),
|
||||||
path.join(process.resourcesPath || process.cwd(), 'resources'),
|
process.env.WCDB_RESOURCES_PATH || '',
|
||||||
process.resourcesPath || process.cwd(),
|
...getResourceRoots()
|
||||||
path.join(process.cwd(), 'resources')
|
].filter(Boolean)
|
||||||
])
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
let lastCode = -1
|
let lastCode = -1
|
||||||
@@ -1373,19 +1374,21 @@ export class Wcdb4Client {
|
|||||||
? 'libwcdb_api.so'
|
? 'libwcdb_api.so'
|
||||||
: 'wcdb_api.dll'
|
: 'wcdb_api.dll'
|
||||||
const platformDir =
|
const platformDir =
|
||||||
process.platform === 'darwin' ? 'macos' : process.platform === 'win32' ? 'win32' : process.platform
|
process.platform === 'darwin'
|
||||||
|
? 'macos'
|
||||||
|
: process.platform === 'win32'
|
||||||
|
? 'win32'
|
||||||
|
: process.platform
|
||||||
const archDir = process.arch === 'arm64' ? 'arm64' : 'x64'
|
const archDir = process.arch === 'arm64' ? 'arm64' : 'x64'
|
||||||
const resourcesPath = process.resourcesPath || process.cwd()
|
const resourceRoots = getResourceRoots()
|
||||||
const candidates = [
|
const candidates = [
|
||||||
process.env.WCDB_DLL_PATH,
|
process.env.WCDB_DLL_PATH,
|
||||||
path.join(resourcesPath, 'resources', 'wcdb', platformDir, archDir, libName),
|
...resourceRoots.flatMap((root) => [
|
||||||
path.join(resourcesPath, 'resources', 'wcdb', platformDir, 'x64', libName),
|
path.join(root, 'wcdb', platformDir, archDir, libName),
|
||||||
path.join(process.cwd(), 'resources', 'wcdb', platformDir, archDir, libName),
|
path.join(root, 'wcdb', platformDir, 'x64', libName),
|
||||||
path.join(process.cwd(), 'resources', 'wcdb', platformDir, 'x64', libName),
|
path.join(root, platformDir, libName),
|
||||||
path.join(resourcesPath, 'resources', platformDir, libName),
|
path.join(root, libName)
|
||||||
path.join(resourcesPath, 'resources', libName),
|
])
|
||||||
path.join(process.cwd(), 'resources', platformDir, libName),
|
|
||||||
path.join(process.cwd(), 'resources', libName)
|
|
||||||
].filter(Boolean) as string[]
|
].filter(Boolean) as string[]
|
||||||
|
|
||||||
const found = candidates.find((candidate) => fs.existsSync(candidate))
|
const found = candidates.find((candidate) => fs.existsSync(candidate))
|
||||||
@@ -1393,9 +1396,13 @@ export class Wcdb4Client {
|
|||||||
throw new Error(`找不到 WCDB native 库: ${candidates.join(', ')}`)
|
throw new Error(`找不到 WCDB native 库: ${candidates.join(', ')}`)
|
||||||
}
|
}
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
const runtimeDir = path.join(resourcesPath, 'resources', 'runtime', 'win32')
|
const runtimeDir = resourceRoots
|
||||||
|
.map((root) => path.join(root, 'runtime', 'win32'))
|
||||||
|
.find((candidate) => fs.existsSync(candidate))
|
||||||
const dllDir = path.dirname(found)
|
const dllDir = path.dirname(found)
|
||||||
process.env.PATH = [dllDir, runtimeDir, process.env.PATH || ''].filter(Boolean).join(path.delimiter)
|
process.env.PATH = [dllDir, runtimeDir || '', process.env.PATH || '']
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(path.delimiter)
|
||||||
}
|
}
|
||||||
return found
|
return found
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -624,6 +624,25 @@ function App(): React.ReactElement {
|
|||||||
setDbKeyStatusKind('normal')
|
setDbKeyStatusKind('normal')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleReturnToLogin = (): void => {
|
||||||
|
setIsAuthenticated(false)
|
||||||
|
setIsDatabaseConnected(false)
|
||||||
|
setBootState('login')
|
||||||
|
setActivePage('archive')
|
||||||
|
setSettingsCategory('database-key')
|
||||||
|
setSelectedContact(null)
|
||||||
|
setMessages([])
|
||||||
|
setContacts([])
|
||||||
|
setFilteredContacts([])
|
||||||
|
setSelfInfo(null)
|
||||||
|
setIsMessagesLoading(false)
|
||||||
|
setIsNativeMonitorActive(false)
|
||||||
|
setReportNotice('')
|
||||||
|
setDbKeyStatus('已断开当前连接,可重新输入或获取数据库密钥')
|
||||||
|
setDbKeyStatusKind('normal')
|
||||||
|
setStartupProgress(null)
|
||||||
|
}
|
||||||
|
|
||||||
const getDateRangeParams = (
|
const getDateRangeParams = (
|
||||||
range: string
|
range: string
|
||||||
): { startTime: number | undefined; endTime: number | undefined } => {
|
): { startTime: number | undefined; endTime: number | undefined } => {
|
||||||
@@ -1140,6 +1159,7 @@ function App(): React.ReactElement {
|
|||||||
onSelfInfoChange={setSelfInfo}
|
onSelfInfoChange={setSelfInfo}
|
||||||
onContactsChange={setContacts}
|
onContactsChange={setContacts}
|
||||||
onFilteredContactsChange={setFilteredContacts}
|
onFilteredContactsChange={setFilteredContacts}
|
||||||
|
onReturnToLogin={handleReturnToLogin}
|
||||||
onAIRuntimeChange={(config: AIRuntimeModelConfig) => setAiModelConfig(config)}
|
onAIRuntimeChange={(config: AIRuntimeModelConfig) => setAiModelConfig(config)}
|
||||||
onNotice={setReportNotice}
|
onNotice={setReportNotice}
|
||||||
onOpenSettings={openSettings}
|
onOpenSettings={openSettings}
|
||||||
|
|||||||
@@ -3825,7 +3825,7 @@ body {
|
|||||||
.database-key-auto-error { display:grid; gap:4px; margin-top:16px; padding:12px 14px; border-left:3px solid #c68635; background:#fff8ec; color:#79501f; font-size:12px; }.database-key-auto-error span { overflow-wrap:anywhere; }.database-key-auto-error p { color:#79501f; }.database-key-auto-error button { justify-self:start; border:0; padding:4px 0; background:transparent; color:#247a63; cursor:pointer; font:600 12px inherit; }
|
.database-key-auto-error { display:grid; gap:4px; margin-top:16px; padding:12px 14px; border-left:3px solid #c68635; background:#fff8ec; color:#79501f; font-size:12px; }.database-key-auto-error span { overflow-wrap:anywhere; }.database-key-auto-error p { color:#79501f; }.database-key-auto-error button { justify-self:start; border:0; padding:4px 0; background:transparent; color:#247a63; cursor:pointer; font:600 12px inherit; }
|
||||||
.database-key-security-info { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:1px; overflow:hidden; border:1px solid #e3e8e5; border-radius:9px; background:#e3e8e5; }.database-key-security-info span { display:grid; gap:6px; padding:15px; background:#f5f7f6; color:#66706b; font-size:12px; line-height:1.5; }.database-key-security-info strong { color:#35403b; font-size:13px; }
|
.database-key-security-info { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:1px; overflow:hidden; border:1px solid #e3e8e5; border-radius:9px; background:#e3e8e5; }.database-key-security-info span { display:grid; gap:6px; padding:15px; background:#f5f7f6; color:#66706b; font-size:12px; line-height:1.5; }.database-key-security-info strong { color:#35403b; font-size:13px; }
|
||||||
.database-key-diagnostics { margin-top:22px; border:1px solid #dde3e0; border-radius:9px; background:#fff; }.database-key-diagnostics summary { padding:14px 16px; color:#46514c; cursor:pointer; font-size:13px; font-weight:600; }.database-key-diagnostics dl { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:14px 28px; margin:0; padding:2px 16px 16px; }.database-key-diagnostics button { margin:0 16px 16px; border:0; padding:0; background:transparent; color:#247a63; cursor:pointer; font:600 12px inherit; }
|
.database-key-diagnostics { margin-top:22px; border:1px solid #dde3e0; border-radius:9px; background:#fff; }.database-key-diagnostics summary { padding:14px 16px; color:#46514c; cursor:pointer; font-size:13px; font-weight:600; }.database-key-diagnostics dl { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:14px 28px; margin:0; padding:2px 16px 16px; }.database-key-diagnostics button { margin:0 16px 16px; border:0; padding:0; background:transparent; color:#247a63; cursor:pointer; font:600 12px inherit; }
|
||||||
.database-key-danger { margin-top:30px; padding-bottom:1px; }.database-key-danger>h2 { margin:0 0 14px; color:#c85a5a; font-size:15px; }.database-key-danger>div { display:flex; align-items:center; justify-content:space-between; gap:20px; padding:16px 20px; border:1px solid #efcdcd; background:#fff7f7; }.database-key-danger>div:first-of-type { border-radius:9px 9px 0 0; }.database-key-danger>div:last-of-type { border-top:0; border-radius:0 0 9px 9px; }.database-key-danger span { display:grid; gap:4px; }.database-key-danger strong { color:#4a3c3c; font-size:13px; }.database-key-danger small { color:#786868; font-size:12px; }.database-key-danger button { min-height:34px; flex:0 0 auto; border:1px solid #e8baba; border-radius:8px; padding:0 13px; background:#fff; color:#c85a5a; cursor:pointer; }.database-key-danger button:disabled { cursor:not-allowed; opacity:.45; }
|
.database-key-connection-actions { margin-top:30px; }.database-key-connection-actions>h2 { margin:0 0 14px; color:#3d4742; font-size:15px; }.database-key-connection-actions>div { display:flex; align-items:center; justify-content:space-between; gap:20px; padding:16px 20px; border:1px solid #dde3e0; border-radius:9px; background:#fff; }.database-key-connection-actions span { display:grid; gap:4px; }.database-key-connection-actions strong { color:#35403b; font-size:13px; }.database-key-connection-actions small { color:#66706b; font-size:12px; }.database-key-connection-actions button { min-height:34px; flex:0 0 auto; border:1px solid #247a63; border-radius:8px; padding:0 13px; background:#fff; color:#247a63; cursor:pointer; }.database-key-connection-actions button:hover:not(:disabled) { background:#f0f7f4; }.database-key-danger { margin-top:30px; padding-bottom:1px; }.database-key-danger>h2 { margin:0 0 14px; color:#c85a5a; font-size:15px; }.database-key-danger>div { display:flex; align-items:center; justify-content:space-between; gap:20px; padding:16px 20px; border:1px solid #efcdcd; background:#fff7f7; }.database-key-danger>div:first-of-type { border-radius:9px 9px 0 0; }.database-key-danger>div:last-of-type { border-top:0; border-radius:0 0 9px 9px; }.database-key-danger span { display:grid; gap:4px; }.database-key-danger strong { color:#4a3c3c; font-size:13px; }.database-key-danger small { color:#786868; font-size:12px; }.database-key-danger button { min-height:34px; flex:0 0 auto; border:1px solid #e8baba; border-radius:8px; padding:0 13px; background:#fff; color:#c85a5a; cursor:pointer; }.database-key-danger button:disabled { cursor:not-allowed; opacity:.45; }
|
||||||
.database-key-confirm-backdrop { position:fixed; z-index:30; inset:0; display:grid; place-items:center; padding:24px; background:rgba(25,32,29,.34); }.database-key-confirm { width:min(100%,430px); padding:22px; border-radius:12px; background:#fff; box-shadow:0 18px 50px rgba(24,35,30,.2); }.database-key-confirm h2 { margin:0; color:#202724; font-size:17px; }.database-key-confirm p { margin:12px 0 22px; color:#66706b; font-size:13px; line-height:1.7; }.database-key-confirm>div { display:flex; justify-content:flex-end; gap:8px; }.database-key-confirm button { min-height:34px; border:1px solid #dde3e0; border-radius:8px; padding:0 14px; background:#fff; color:#46514c; cursor:pointer; }.database-key-confirm button.danger { border-color:#c85a5a; background:#c85a5a; color:#fff; }
|
.database-key-confirm-backdrop { position:fixed; z-index:30; inset:0; display:grid; place-items:center; padding:24px; background:rgba(25,32,29,.34); }.database-key-confirm { width:min(100%,430px); padding:22px; border-radius:12px; background:#fff; box-shadow:0 18px 50px rgba(24,35,30,.2); }.database-key-confirm h2 { margin:0; color:#202724; font-size:17px; }.database-key-confirm p { margin:12px 0 22px; color:#66706b; font-size:13px; line-height:1.7; }.database-key-confirm>div { display:flex; justify-content:flex-end; gap:8px; }.database-key-confirm button { min-height:34px; border:1px solid #dde3e0; border-radius:8px; padding:0 14px; background:#fff; color:#46514c; cursor:pointer; }.database-key-confirm button.danger { border-color:#c85a5a; background:#c85a5a; color:#fff; }
|
||||||
/* SETTINGS-03: image decryption */
|
/* SETTINGS-03: image decryption */
|
||||||
.image-decryption-content { padding-bottom:48px; }
|
.image-decryption-content { padding-bottom:48px; }
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export function SettingsWorkspace({
|
|||||||
onSelfInfoChange,
|
onSelfInfoChange,
|
||||||
onContactsChange,
|
onContactsChange,
|
||||||
onFilteredContactsChange,
|
onFilteredContactsChange,
|
||||||
|
onReturnToLogin,
|
||||||
onAIRuntimeChange,
|
onAIRuntimeChange,
|
||||||
onNotice,
|
onNotice,
|
||||||
onOpenSettings
|
onOpenSettings
|
||||||
@@ -34,6 +35,7 @@ export function SettingsWorkspace({
|
|||||||
onSelfInfoChange: (info: SettingsSelfInfo | null) => void
|
onSelfInfoChange: (info: SettingsSelfInfo | null) => void
|
||||||
onContactsChange: (contacts: Contact[]) => void
|
onContactsChange: (contacts: Contact[]) => void
|
||||||
onFilteredContactsChange: (contacts: Contact[]) => void
|
onFilteredContactsChange: (contacts: Contact[]) => void
|
||||||
|
onReturnToLogin: () => void
|
||||||
onAIRuntimeChange: (config: AIRuntimeModelConfig) => void
|
onAIRuntimeChange: (config: AIRuntimeModelConfig) => void
|
||||||
onNotice: (message: string) => void
|
onNotice: (message: string) => void
|
||||||
onOpenSettings: () => void
|
onOpenSettings: () => void
|
||||||
@@ -64,6 +66,7 @@ export function SettingsWorkspace({
|
|||||||
onSelfInfoChange={onSelfInfoChange}
|
onSelfInfoChange={onSelfInfoChange}
|
||||||
onContactsChange={onContactsChange}
|
onContactsChange={onContactsChange}
|
||||||
onFilteredContactsChange={onFilteredContactsChange}
|
onFilteredContactsChange={onFilteredContactsChange}
|
||||||
|
onReturnToLogin={onReturnToLogin}
|
||||||
onNotice={onNotice}
|
onNotice={onNotice}
|
||||||
/>
|
/>
|
||||||
) : selectedCategory === 'image-key' ? (
|
) : selectedCategory === 'image-key' ? (
|
||||||
|
|||||||
@@ -3,15 +3,30 @@ import { useState } from 'react'
|
|||||||
export function DatabaseKeyDangerZone({
|
export function DatabaseKeyDangerZone({
|
||||||
disabled,
|
disabled,
|
||||||
onClear,
|
onClear,
|
||||||
onReplace
|
onReplace,
|
||||||
|
onReturnToLogin
|
||||||
}: {
|
}: {
|
||||||
disabled: boolean
|
disabled: boolean
|
||||||
onClear: () => void
|
onClear: () => void
|
||||||
onReplace: () => void
|
onReplace: () => void
|
||||||
|
onReturnToLogin: () => void
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
const [confirming, setConfirming] = useState(false)
|
const [confirming, setConfirming] = useState(false)
|
||||||
|
const [confirmingReturn, setConfirmingReturn] = useState(false)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<section className="database-key-connection-actions">
|
||||||
|
<h2>连接管理</h2>
|
||||||
|
<div>
|
||||||
|
<span>
|
||||||
|
<strong>返回登录界面</strong>
|
||||||
|
<small>断开当前数据库连接,回到密钥输入界面。不会删除已保存密钥或微信数据。</small>
|
||||||
|
</span>
|
||||||
|
<button type="button" onClick={() => setConfirmingReturn(true)}>
|
||||||
|
返回登录
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
<section className="database-key-danger">
|
<section className="database-key-danger">
|
||||||
<h2>密钥管理</h2>
|
<h2>密钥管理</h2>
|
||||||
<div>
|
<div>
|
||||||
@@ -69,6 +84,41 @@ export function DatabaseKeyDangerZone({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{confirmingReturn && (
|
||||||
|
<div
|
||||||
|
className="database-key-confirm-backdrop"
|
||||||
|
role="presentation"
|
||||||
|
onMouseDown={() => setConfirmingReturn(false)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="database-key-confirm"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-labelledby="database-key-return-confirm-title"
|
||||||
|
onMouseDown={(event) => event.stopPropagation()}
|
||||||
|
>
|
||||||
|
<h2 id="database-key-return-confirm-title">返回登录界面?</h2>
|
||||||
|
<p>
|
||||||
|
WechatExplorer
|
||||||
|
将断开当前数据库连接并回到密钥输入界面。已保存的数据库密钥和微信原始数据不会被删除。
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<button type="button" onClick={() => setConfirmingReturn(false)}>
|
||||||
|
取消
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setConfirmingReturn(false)
|
||||||
|
onReturnToLogin()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
返回登录
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ export interface DatabaseKeyController {
|
|||||||
saveKey: () => Promise<void>
|
saveKey: () => Promise<void>
|
||||||
autoDetectKey: () => Promise<void>
|
autoDetectKey: () => Promise<void>
|
||||||
clearSavedKey: () => Promise<void>
|
clearSavedKey: () => Promise<void>
|
||||||
|
returnToLogin: () => Promise<void>
|
||||||
copyDiagnostics: () => Promise<void>
|
copyDiagnostics: () => Promise<void>
|
||||||
refreshEnvironment: () => Promise<void>
|
refreshEnvironment: () => Promise<void>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export function useDatabaseKeyController({
|
|||||||
onSelfInfoChange,
|
onSelfInfoChange,
|
||||||
onContactsChange,
|
onContactsChange,
|
||||||
onFilteredContactsChange,
|
onFilteredContactsChange,
|
||||||
|
onReturnToLogin,
|
||||||
onNotice
|
onNotice
|
||||||
}: {
|
}: {
|
||||||
dbKey: string
|
dbKey: string
|
||||||
@@ -24,6 +25,7 @@ export function useDatabaseKeyController({
|
|||||||
onSelfInfoChange: (info: SettingsSelfInfo | null) => void
|
onSelfInfoChange: (info: SettingsSelfInfo | null) => void
|
||||||
onContactsChange: (contacts: Contact[]) => void
|
onContactsChange: (contacts: Contact[]) => void
|
||||||
onFilteredContactsChange: (contacts: Contact[]) => void
|
onFilteredContactsChange: (contacts: Contact[]) => void
|
||||||
|
onReturnToLogin: () => void
|
||||||
onNotice: (message: string) => void
|
onNotice: (message: string) => void
|
||||||
}): DatabaseKeyController {
|
}): DatabaseKeyController {
|
||||||
const [state, dispatch] = useReducer(databaseKeyReducer, initialDatabaseKeyState)
|
const [state, dispatch] = useReducer(databaseKeyReducer, initialDatabaseKeyState)
|
||||||
@@ -188,6 +190,26 @@ export function useDatabaseKeyController({
|
|||||||
refreshEnvironment
|
refreshEnvironment
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const returnToLogin = useCallback(async (): Promise<void> => {
|
||||||
|
const result = await window.api.disconnectDb()
|
||||||
|
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 copyDiagnostics = useCallback(async (): Promise<void> => {
|
||||||
const result = await window.api.copyText(
|
const result = await window.api.copyText(
|
||||||
buildDatabaseKeyDiagnostics(state, dbKey, selfInfo, dbReady)
|
buildDatabaseKeyDiagnostics(state, dbKey, selfInfo, dbReady)
|
||||||
@@ -214,6 +236,7 @@ export function useDatabaseKeyController({
|
|||||||
saveKey,
|
saveKey,
|
||||||
autoDetectKey,
|
autoDetectKey,
|
||||||
clearSavedKey,
|
clearSavedKey,
|
||||||
|
returnToLogin,
|
||||||
copyDiagnostics,
|
copyDiagnostics,
|
||||||
refreshEnvironment
|
refreshEnvironment
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export function DatabaseKeyPage({
|
|||||||
onSelfInfoChange,
|
onSelfInfoChange,
|
||||||
onContactsChange,
|
onContactsChange,
|
||||||
onFilteredContactsChange,
|
onFilteredContactsChange,
|
||||||
|
onReturnToLogin,
|
||||||
onNotice
|
onNotice
|
||||||
}: {
|
}: {
|
||||||
dbKey: string
|
dbKey: string
|
||||||
@@ -34,6 +35,7 @@ export function DatabaseKeyPage({
|
|||||||
onSelfInfoChange: (info: SettingsSelfInfo | null) => void
|
onSelfInfoChange: (info: SettingsSelfInfo | null) => void
|
||||||
onContactsChange: (contacts: Contact[]) => void
|
onContactsChange: (contacts: Contact[]) => void
|
||||||
onFilteredContactsChange: (contacts: Contact[]) => void
|
onFilteredContactsChange: (contacts: Contact[]) => void
|
||||||
|
onReturnToLogin: () => void
|
||||||
onNotice: (message: string) => void
|
onNotice: (message: string) => void
|
||||||
}): React.ReactElement {
|
}): React.ReactElement {
|
||||||
const controller = useDatabaseKeyController({
|
const controller = useDatabaseKeyController({
|
||||||
@@ -45,6 +47,7 @@ export function DatabaseKeyPage({
|
|||||||
onSelfInfoChange,
|
onSelfInfoChange,
|
||||||
onContactsChange,
|
onContactsChange,
|
||||||
onFilteredContactsChange,
|
onFilteredContactsChange,
|
||||||
|
onReturnToLogin,
|
||||||
onNotice
|
onNotice
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -132,6 +135,7 @@ export function DatabaseKeyPage({
|
|||||||
disabled={controller.isBusy || !controller.state.saved}
|
disabled={controller.isBusy || !controller.state.saved}
|
||||||
onClear={() => void controller.clearSavedKey()}
|
onClear={() => void controller.clearSavedKey()}
|
||||||
onReplace={scrollToEditor}
|
onReplace={scrollToEditor}
|
||||||
|
onReturnToLogin={() => void controller.returnToLogin()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user