Files
CipherTalk/electron/services/wcdbService.ts
T

301 lines
8.6 KiB
TypeScript

import { join, dirname } from 'path'
import { existsSync, readdirSync, statSync } from 'fs'
import { app } from 'electron'
export class WcdbService {
private lib: any = null
private koffi: any = null
private initialized = false
private handle: number | null = null // 改为 number 类型
// 函数引用
private wcdbInit: any = null
private wcdbShutdown: any = null
private wcdbOpenAccount: any = null
private wcdbCloseAccount: any = null
private wcdbFreeString: any = null
private wcdbGetSessions: any = null
private wcdbGetLogs: any = null
/**
* 获取 DLL 路径
*/
private getDllPath(): string {
const resourcesPath = app.isPackaged
? join(process.resourcesPath, 'resources')
: join(app.getAppPath(), 'resources')
return join(resourcesPath, 'wcdb_api.dll')
}
/**
* 递归查找 session.db 文件
*/
private findSessionDb(dir: string, depth = 0): string | null {
if (depth > 5) return null
try {
const entries = readdirSync(dir)
for (const entry of entries) {
if (entry.toLowerCase() === 'session.db') {
const fullPath = join(dir, entry)
if (statSync(fullPath).isFile()) {
return fullPath
}
}
}
for (const entry of entries) {
const fullPath = join(dir, entry)
try {
if (statSync(fullPath).isDirectory()) {
const found = this.findSessionDb(fullPath, depth + 1)
if (found) return found
}
} catch { }
}
} catch (e) {
console.error('查找 session.db 失败:', e)
}
return null
}
/**
* 初始化 wcdb 库
* 返回: { success: boolean, error?: string }
*/
private async initialize(): Promise<{ success: boolean; error?: string }> {
if (this.initialized) return { success: true }
try {
this.koffi = require('koffi')
const dllPath = this.getDllPath()
if (!existsSync(dllPath)) {
const msg = `WCDB DLL 不存在: ${dllPath}`
console.error(msg)
return { success: false, error: msg }
}
// 关键修复:显式预加载依赖库 WCDB.dll
const wcdbCorePath = join(dirname(dllPath), 'WCDB.dll')
if (existsSync(wcdbCorePath)) {
try {
this.koffi.load(wcdbCorePath)
} catch (e: any) {
console.warn('预加载 WCDB.dll 失败:', e)
// 不要在这里返回失败,尝试继续加载主 DLL
}
} else {
console.warn('预加载警告: WCDB.dll 未找到', wcdbCorePath)
}
// 尝试加载主 DLL
try {
this.lib = this.koffi.load(dllPath)
} catch (e: any) {
const msg = `koffi.load(wcdb_api) 失败: ${e.message}`
console.error(msg)
return { success: false, error: msg }
}
// 定义类型 - 使用与 C 接口完全匹配的签名
// wcdb_status wcdb_init()
this.wcdbInit = this.lib.func('int32 wcdb_init()')
// wcdb_status wcdb_shutdown()
this.wcdbShutdown = this.lib.func('int32 wcdb_shutdown()')
// wcdb_status wcdb_open_account(const char* session_db_path, const char* hex_key, wcdb_handle* out_handle)
this.wcdbOpenAccount = this.lib.func('int32 wcdb_open_account(const char* path, const char* key, _Out_ int64* handle)')
// wcdb_status wcdb_close_account(wcdb_handle handle)
this.wcdbCloseAccount = this.lib.func('int32 wcdb_close_account(int64 handle)')
// void wcdb_free_string(char* ptr)
this.wcdbFreeString = this.lib.func('void wcdb_free_string(void* ptr)')
// wcdb_status wcdb_get_sessions(wcdb_handle handle, char** out_json)
this.wcdbGetSessions = this.lib.func('int32 wcdb_get_sessions(int64 handle, _Out_ void** outJson)')
// wcdb_status wcdb_get_logs(char** out_json)
this.wcdbGetLogs = this.lib.func('int32 wcdb_get_logs(_Out_ void** outJson)')
// 初始化
const initResult = this.wcdbInit()
if (initResult !== 0) {
const msg = `WCDB wcdb_init() 返回错误码: ${initResult}`
console.error(msg)
return { success: false, error: msg }
}
this.initialized = true
return { success: true }
} catch (e: any) {
console.error('WCDB 初始化异常:', e)
return { success: false, error: `初始化异常: ${e.message}` }
}
}
/**
* 测试数据库连接
*/
async testConnection(dbPath: string, hexKey: string, wxid: string): Promise<{ success: boolean; error?: string; sessionCount?: number }> {
try {
if (!this.initialized) {
const initRes = await this.initialize()
if (!initRes.success) {
return { success: false, error: initRes.error || 'WCDB 初始化失败(未知原因)' }
}
}
// 构建 db_storage 目录路径
const dbStoragePath = join(dbPath, wxid, 'db_storage')
if (!existsSync(dbStoragePath)) {
return { success: false, error: `数据库目录不存在: ${dbStoragePath}` }
}
// 递归查找 session.db
const sessionDbPath = this.findSessionDb(dbStoragePath)
if (!sessionDbPath) {
return { success: false, error: `未找到 session.db 文件` }
}
// 分配输出参数内存 - 使用 number 数组
const handleOut = [0]
const result = this.wcdbOpenAccount(sessionDbPath, hexKey, handleOut)
if (result !== 0) {
// 获取 DLL 内部日志
await this.printLogs()
let errorMsg = '数据库打开失败'
if (result === -1) errorMsg = '参数错误'
else if (result === -2) errorMsg = '密钥错误'
else if (result === -3) errorMsg = '数据库打开失败'
return { success: false, error: `${errorMsg} (错误码: ${result})` }
}
const handle = handleOut[0]
if (handle <= 0) {
return { success: false, error: '无效的数据库句柄' }
}
// 连接成功,直接关闭
// 注意:wcdb_close_account 可能导致崩溃,使用 shutdown 代替
try {
// 不调用 closeAccount,直接 shutdown(会释放所有句柄)
this.wcdbShutdown()
this.initialized = false // 标记需要重新初始化
} catch (closeErr) {
console.error('关闭数据库时出错:', closeErr)
}
return { success: true, sessionCount: 0 }
} catch (e) {
console.error('测试连接异常:', e)
return { success: false, error: String(e) }
}
}
/**
* 打印 DLL 内部日志(仅在出错时调用)
*/
private async printLogs(): Promise<void> {
try {
if (!this.wcdbGetLogs) return
const outPtr = [null as any]
const result = this.wcdbGetLogs(outPtr)
if (result === 0 && outPtr[0]) {
try {
const jsonStr = this.koffi.decode(outPtr[0], 'char', -1)
console.error('WCDB 内部日志:', jsonStr)
this.wcdbFreeString(outPtr[0])
} catch (e) {
// ignore
}
}
} catch (e) {
console.error('获取日志失败:', e)
}
}
/**
* 打开数据库
*/
async open(dbPath: string, hexKey: string, wxid: string): Promise<boolean> {
try {
if (!this.initialized) {
const initOk = await this.initialize()
if (!initOk) return false
}
if (this.handle !== null) {
this.close()
}
const dbStoragePath = join(dbPath, wxid, 'db_storage')
if (!existsSync(dbStoragePath)) {
console.error('数据库目录不存在:', dbStoragePath)
return false
}
const sessionDbPath = this.findSessionDb(dbStoragePath)
if (!sessionDbPath) {
console.error('未找到 session.db 文件')
return false
}
const handleOut = [0] // 使用 number 而不是 BigInt
const result = this.wcdbOpenAccount(sessionDbPath, hexKey, handleOut)
if (result !== 0) {
console.error('打开数据库失败:', result)
return false
}
const handle = handleOut[0]
if (handle <= 0) {
return false
}
this.handle = handle
return true
} catch (e) {
console.error('打开数据库异常:', e)
return false
}
}
/**
* 关闭数据库
* 注意:wcdb_close_account 可能导致崩溃,使用 shutdown 代替
*/
close(): void {
if (this.handle !== null || this.initialized) {
try {
// 不调用 closeAccount,直接 shutdown
this.wcdbShutdown()
} catch (e) {
console.error('WCDB shutdown 出错:', e)
}
this.handle = null
this.initialized = false
}
}
/**
* 关闭服务(与 close 相同)
*/
shutdown(): void {
this.close()
}
}
export const wcdbService = new WcdbService()