feat: 一些优化

This commit is contained in:
cc
2026-01-30 23:47:46 +08:00
parent 7225358b91
commit e4ed7faca9
20 changed files with 222 additions and 725 deletions

View File

@@ -20,6 +20,7 @@ import { voiceTranscribeService } from './services/voiceTranscribeService'
import { videoService } from './services/videoService'
import { snsService } from './services/snsService'
import { contactExportService } from './services/contactExportService'
import { windowsHelloService } from './services/windowsHelloService'
// 配置自动更新
@@ -798,6 +799,17 @@ function registerIpcHandlers() {
return true
})
// Windows Hello
ipcMain.handle('auth:hello', async (event, message?: string) => {
// 无论哪个窗口调用,都尝试强制附着到主窗口,确保体验一致
// 如果主窗口不存在(极其罕见),则回退到调用者窗口
const targetWin = (mainWindow && !mainWindow.isDestroyed())
? mainWindow
: (BrowserWindow.fromWebContents(event.sender) || undefined)
return windowsHelloService.verify(message, targetWin)
})
// 导出相关
ipcMain.handle('export:exportSessions', async (event, sessionIds: string[], outputDir: string, options: ExportOptions) => {
const onProgress = (progress: ExportProgress) => {

24
electron/nodert.d.ts vendored Normal file
View File

@@ -0,0 +1,24 @@
declare module '@nodert-win10-rs4/windows.security.credentials.ui' {
export enum UserConsentVerificationResult {
Verified = 0,
DeviceNotPresent = 1,
NotConfiguredForUser = 2,
DisabledByPolicy = 3,
DeviceBusy = 4,
RetriesExhausted = 5,
Canceled = 6
}
export enum UserConsentVerifierAvailability {
Available = 0,
DeviceNotPresent = 1,
NotConfiguredForUser = 2,
DisabledByPolicy = 3,
DeviceBusy = 4
}
export class UserConsentVerifier {
static checkAvailabilityAsync(callback: (err: Error | null, availability: UserConsentVerifierAvailability) => void): void;
static requestVerificationAsync(message: string, callback: (err: Error | null, result: UserConsentVerificationResult) => void): void;
}
}

View File

@@ -9,6 +9,11 @@ contextBridge.exposeInMainWorld('electronAPI', {
clear: () => ipcRenderer.invoke('config:clear')
},
// 认证
auth: {
hello: (message?: string) => ipcRenderer.invoke('auth:hello', message)
},
// 对话框
dialog: {

View File

@@ -57,6 +57,7 @@ export class WcdbCore {
private wcdbGetDbStatus: any = null
private wcdbGetVoiceData: any = null
private wcdbGetSnsTimeline: any = null
private wcdbVerifyUser: any = null
private avatarUrlCache: Map<string, { url?: string; updatedAt: number }> = new Map()
private readonly avatarCacheTtlMs = 10 * 60 * 1000
private logTimer: NodeJS.Timeout | null = null
@@ -247,7 +248,7 @@ export class WcdbCore {
// InitProtection (Added for security)
try {
this.wcdbInitProtection = this.lib.func('bool InitProtection(const char* resourcePath)')
// 尝试多个可能的资源路径
const resourcePaths = [
dllDir, // DLL 所在目录
@@ -255,28 +256,28 @@ export class WcdbCore {
this.resourcesPath, // 配置的资源路径
join(process.cwd(), 'resources') // 开发环境
].filter(Boolean)
let protectionOk = false
for (const resPath of resourcePaths) {
try {
console.log(`[WCDB] 尝试 InitProtection: ${resPath}`)
// console.log(`[WCDB] 尝试 InitProtection: ${resPath}`)
protectionOk = this.wcdbInitProtection(resPath)
if (protectionOk) {
console.log(`[WCDB] InitProtection 成功: ${resPath}`)
// console.log(`[WCDB] InitProtection 成功: ${resPath}`)
break
}
} catch (e) {
console.warn(`[WCDB] InitProtection 失败 (${resPath}):`, e)
// console.warn(`[WCDB] InitProtection 失败 (${resPath}):`, e)
}
}
if (!protectionOk) {
console.warn('[WCDB] Core security check failed - 继续运行但可能不稳定')
this.writeLog('InitProtection 失败,继续运行')
// console.warn('[WCDB] Core security check failed - 继续运行但可能不稳定')
// this.writeLog('InitProtection 失败,继续运行')
// 不返回 false允许继续运行
}
} catch (e) {
console.warn('InitProtection symbol not found:', e)
// console.warn('InitProtection symbol not found:', e)
}
// 定义类型
@@ -430,6 +431,13 @@ export class WcdbCore {
this.wcdbGetSnsTimeline = null
}
// void VerifyUser(int64_t hwnd_ptr, const char* message, char* out_result, int max_len)
try {
this.wcdbVerifyUser = this.lib.func('void VerifyUser(int64 hwnd, const char* message, _Out_ char* outResult, int maxLen)')
} catch {
this.wcdbVerifyUser = null
}
// 初始化
const initResult = this.wcdbInit()
if (initResult !== 0) {
@@ -1434,6 +1442,39 @@ export class WcdbCore {
}
}
/**
* 验证 Windows Hello
*/
async verifyUser(message: string, hwnd?: string): Promise<{ success: boolean; error?: string }> {
if (!this.initialized) {
const initOk = await this.initialize()
if (!initOk) return { success: false, error: 'WCDB 初始化失败' }
}
if (!this.wcdbVerifyUser) {
return { success: false, error: 'Binding not found: VerifyUser' }
}
return new Promise((resolve) => {
try {
// Allocate buffer for result JSON
const maxLen = 1024
const outBuf = Buffer.alloc(maxLen)
// Call native function
const hwndVal = hwnd ? BigInt(hwnd) : BigInt(0)
this.wcdbVerifyUser(hwndVal, message || '', outBuf, maxLen)
// Parse result
const jsonStr = this.koffi.decode(outBuf, 'char', -1)
const result = JSON.parse(jsonStr)
resolve(result)
} catch (e) {
resolve({ success: false, error: String(e) })
}
})
}
async getSnsTimeline(limit: number, offset: number, usernames?: string[], keyword?: string, startTime?: number, endTime?: number): Promise<{ success: boolean; timeline?: any[]; error?: string }> {
if (!this.ensureReady()) return { success: false, error: 'WCDB 未连接' }
if (!this.wcdbGetSnsTimeline) return { success: false, error: '当前 DLL 版本不支持获取朋友圈' }

View File

@@ -369,6 +369,13 @@ export class WcdbService {
return this.callWorker('getSnsTimeline', { limit, offset, usernames, keyword, startTime, endTime })
}
/**
* 验证 Windows Hello
*/
async verifyUser(message: string, hwnd?: string): Promise<{ success: boolean; error?: string }> {
return this.callWorker('verifyUser', { message, hwnd })
}
}
export const wcdbService = new WcdbService()

View File

@@ -0,0 +1,32 @@
import { wcdbService } from './wcdbService'
import { BrowserWindow } from 'electron'
export class WindowsHelloService {
private verificationPromise: Promise<{ success: boolean; error?: string }> | null = null
/**
* 验证 Windows Hello
* @param message 提示信息
*/
async verify(message: string = '请验证您的身份以解锁 WeFlow', targetWindow?: BrowserWindow): Promise<{ success: boolean; error?: string }> {
// Prevent concurrent verification requests
if (this.verificationPromise) {
return this.verificationPromise
}
// 获取窗口句柄: 优先使用传入的窗口,否则尝试获取焦点窗口,最后兜底主窗口
const window = targetWindow || BrowserWindow.getFocusedWindow() || BrowserWindow.getAllWindows()[0]
const hwndBuffer = window?.getNativeWindowHandle()
// Convert buffer to int string for transport
const hwndStr = hwndBuffer ? BigInt('0x' + hwndBuffer.toString('hex')).toString() : undefined
this.verificationPromise = wcdbService.verifyUser(message, hwndStr)
.finally(() => {
this.verificationPromise = null
})
return this.verificationPromise
}
}
export const windowsHelloService = new WindowsHelloService()

View File

@@ -119,6 +119,9 @@ if (parentPort) {
case 'getSnsTimeline':
result = await core.getSnsTimeline(payload.limit, payload.offset, payload.usernames, payload.keyword, payload.startTime, payload.endTime)
break
case 'verifyUser':
result = await core.verifyUser(payload.message, payload.hwnd)
break
default:
result = { success: false, error: `Unknown method: ${type}` }
}