diff --git a/resources/xkey_helper b/resources/xkey_helper index ada9a32..1c9b951 100755 Binary files a/resources/xkey_helper and b/resources/xkey_helper differ diff --git a/resources/xkey_helper_4_1_13 b/resources/xkey_helper_4_1_13 new file mode 100755 index 0000000..ada9a32 Binary files /dev/null and b/resources/xkey_helper_4_1_13 differ diff --git a/resources/xkey_helper.sha256 b/resources/xkey_helper_4_1_13.sha256 similarity index 75% rename from resources/xkey_helper.sha256 rename to resources/xkey_helper_4_1_13.sha256 index e69d8a2..47562e3 100644 --- a/resources/xkey_helper.sha256 +++ b/resources/xkey_helper_4_1_13.sha256 @@ -1 +1 @@ -f1bbd88584075c0e6d3487357a06424949de26d7e6dcad803d210aab83b0cbda xkey_helper +f1bbd88584075c0e6d3487357a06424949de26d7e6dcad803d210aab83b0cbda xkey_helper_4_1_13 diff --git a/src/main/key-service-mac.ts b/src/main/key-service-mac.ts index b71f3fe..f274c60 100644 --- a/src/main/key-service-mac.ts +++ b/src/main/key-service-mac.ts @@ -6,6 +6,7 @@ import { promisify } from 'util' import { isValidDatabaseKey } from './database-key-store' import crypto from 'crypto' import { findResource, getResourceCandidates } from './resource-paths' +import { detectWechatVersion } from './services/connection-diagnostics' const execFileAsync = promisify(execFile) @@ -24,10 +25,42 @@ export interface ImageKeyResult { error?: string } +export type XkeyHelperMode = 'legacy' | 'wechat-4.1.13' + +export function resolveXkeyHelperMode(wechatVersion: string): XkeyHelperMode { + return /^4\.1\.13(?:\.|$)/.test(wechatVersion.trim()) ? 'wechat-4.1.13' : 'legacy' +} + export function buildXkeyHelperArguments(pid: number, timeoutMs: number): string[] { return [String(pid), String(timeoutMs), '--profile', 'wechat-4.1.13', '--account'] } +export function buildAppleSiliconXkeyInvocation( + wechatVersion: string, + pid: number, + timeoutMs: number +): { + mode: XkeyHelperMode + resourceName: string + args: string[] + waitMs: number + timeoutSeconds: number + execTimeoutMs: number +} { + const mode = resolveXkeyHelperMode(wechatVersion) + const isWechat413 = mode === 'wechat-4.1.13' + const waitMs = Math.max(isWechat413 ? 120_000 : 30_000, timeoutMs) + const timeoutSeconds = Math.ceil(waitMs / 1000) + (isWechat413 ? 10 : 30) + return { + mode, + resourceName: isWechat413 ? 'xkey_helper_4_1_13' : 'xkey_helper', + args: isWechat413 ? buildXkeyHelperArguments(pid, waitMs) : [String(pid), String(waitMs)], + waitMs, + timeoutSeconds, + execTimeoutMs: isWechat413 ? timeoutSeconds * 1000 + 5_000 : waitMs + 20_000 + } +} + export function mapXkeyHelperFailure( rawError: string, fallbackCode = 'HELPER_RESULT_INVALID' @@ -260,11 +293,11 @@ export class KeyServiceMac { } } - private getHelperPath(): string { - const helperPath = findResource('xkey_helper') + private getHelperPath(resourceName = 'xkey_helper'): string { + const helperPath = findResource(resourceName) if (!helperPath) { throw new Error( - `找不到 xkey_helper(已检查:${getResourceCandidates('xkey_helper').join(';')})` + `找不到 ${resourceName}(已检查:${getResourceCandidates(resourceName).join(';')})` ) } return helperPath @@ -326,17 +359,17 @@ export class KeyServiceMac { onStatus?.(result.success ? '密钥获取成功' : '密钥获取失败') return result } + const wechatVersion = await detectWechatVersion() onStatus?.('正在查找微信进程...') const pid = await this.getWeChatPid() - const helperPath = this.getHelperPath() - const waitMs = Math.max(120_000, timeoutMs) - const timeoutSeconds = Math.ceil(waitMs / 1000) + 10 - const helperArguments = buildXkeyHelperArguments(pid, waitMs) + const invocation = buildAppleSiliconXkeyInvocation(wechatVersion, pid, timeoutMs) + const isWechat413 = invocation.mode === 'wechat-4.1.13' + const helperPath = this.getHelperPath(invocation.resourceName) onStatus?.('正在请求管理员授权...') const scriptLines = [ `set helperPath to ${JSON.stringify(helperPath)}`, - `set cmd to quoted form of helperPath & " ${helperArguments.join(' ')}"`, - `set timeoutSec to ${timeoutSeconds}`, + `set cmd to quoted form of helperPath & " ${invocation.args.join(' ')}"`, + `set timeoutSec to ${invocation.timeoutSeconds}`, 'try', 'with timeout of timeoutSec seconds', 'set outText to do shell script cmd with administrator privileges', @@ -346,11 +379,15 @@ export class KeyServiceMac { 'return "ERR::" & errNum & "::" & errMsg', 'end try' ] - onStatus?.('授权后请在微信登录界面点击“登录”,已有登录凭据时通常不需要扫码') + onStatus?.( + isWechat413 + ? '授权后请在微信登录界面点击“登录”,已有登录凭据时通常不需要扫码' + : '授权后 需要在微信登录界面 点击登录微信' + ) const { stdout } = await execFileAsync( '/usr/bin/osascript', scriptLines.flatMap((line) => ['-e', line]), - { timeout: timeoutSeconds * 1000 + 5_000 } + { timeout: invocation.execTimeoutMs } ) const output = String(stdout).trim() if (output.startsWith('ERR::-128')) { diff --git a/tests/unit/key-service-mac.test.ts b/tests/unit/key-service-mac.test.ts index 3d8b894..251843e 100644 --- a/tests/unit/key-service-mac.test.ts +++ b/tests/unit/key-service-mac.test.ts @@ -7,9 +7,11 @@ vi.mock('electron', () => ({ })) import { + buildAppleSiliconXkeyInvocation, buildXkeyHelperArguments, mapXkeyHelperFailure, - parseXkeyHelperOutput + parseXkeyHelperOutput, + resolveXkeyHelperMode } from '../../src/main/key-service-mac' describe('parseXkeyHelperOutput', () => { @@ -71,7 +73,19 @@ describe('parseXkeyHelperOutput', () => { }) }) - it('uses the verified 4.1.13 account-key capture contract', () => { + it.each([ + ['4.1.8', 'legacy'], + ['4.1.9.57', 'legacy'], + ['4.1.10', 'legacy'], + ['4.1.13', 'wechat-4.1.13'], + ['4.1.13.91', 'wechat-4.1.13'], + ['4.1.130', 'legacy'], + ['未检测到', 'legacy'] + ])('selects the helper mode for WeChat %s', (version, mode) => { + expect(resolveXkeyHelperMode(version)).toBe(mode) + }) + + it('builds the verified 4.1.13 account-key capture contract', () => { expect(buildXkeyHelperArguments(60037, 120_000)).toEqual([ '60037', '120000', @@ -81,6 +95,31 @@ describe('parseXkeyHelperOutput', () => { ]) }) + it('uses the new helper only for WeChat 4.1.13', () => { + expect(buildAppleSiliconXkeyInvocation('4.1.13.91', 60037, 60_000)).toEqual({ + mode: 'wechat-4.1.13', + resourceName: 'xkey_helper_4_1_13', + args: ['60037', '120000', '--profile', 'wechat-4.1.13', '--account'], + waitMs: 120_000, + timeoutSeconds: 130, + execTimeoutMs: 135_000 + }) + }) + + it.each(['4.1.8', '4.1.9.57', '4.1.10', '未检测到'])( + 'preserves the legacy helper invocation for WeChat %s', + (version) => { + expect(buildAppleSiliconXkeyInvocation(version, 60037, 60_000)).toEqual({ + mode: 'legacy', + resourceName: 'xkey_helper', + args: ['60037', '60000'], + waitMs: 60_000, + timeoutSeconds: 90, + execTimeoutMs: 80_000 + }) + } + ) + it('extracts a helper error embedded in AppleScript diagnostics', () => { expect( mapXkeyHelperFailure(