From cd427ede80ab2b628df644f886dfc18b8f8b3087 Mon Sep 17 00:00:00 2001 From: huangzhenpc Date: Mon, 9 Mar 2026 20:18:02 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=AF=86=E9=92=A5=E5=A4=B1=E6=95=88?= =?UTF-8?q?=E5=90=8E=E8=87=AA=E5=8A=A8=E6=B8=85=E9=99=A4=E5=B9=B6=E5=88=B7?= =?UTF-8?q?=E6=96=B0UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _checkKeyStatus 清除无效密钥后重新调用 _sendState() 刷新面板 - 区分网络离线和后端明确返回无效,避免离线时误清密钥 Co-Authored-By: Claude Opus 4.6 --- extension_clean/out/webview/provider.js | 217 +++++++++++++++++++----- 1 file changed, 176 insertions(+), 41 deletions(-) diff --git a/extension_clean/out/webview/provider.js b/extension_clean/out/webview/provider.js index 932079f..50d0046 100644 --- a/extension_clean/out/webview/provider.js +++ b/extension_clean/out/webview/provider.js @@ -186,6 +186,22 @@ class HummingbirdProViewProvider { this._sendState(); this._checkKeyStatus(); } + /** + * 获取当前激活的密钥(统一入口) + * 优先返回当前选中号池的密钥,兼容旧版本的 hummingbird.key + */ + _getActiveKey() { + const selectedPool = this._context.globalState.get("hummingbird.selectedPool") || "auto"; + const key = selectedPool === "pro" + ? this._context.globalState.get("hummingbird.proKey") + : this._context.globalState.get("hummingbird.autoKey"); + + // 兼容旧版本:如果新字段没有值,回退到旧字段 + if (!key) { + return this._context.globalState.get("hummingbird.key"); + } + return key; + } async _checkKeyStatus() { // 检查 Auto 和 Pro 两个密钥的状态 const autoKey = this._context.globalState.get("hummingbird.autoKey"); @@ -205,6 +221,9 @@ class HummingbirdProViewProvider { try { const verifyResult = await client_1.verifyKey(autoKey); if (verifyResult.success && verifyResult.valid) { + if (verifyResult.key && verifyResult.key !== autoKey) { + await this._context.globalState.update("hummingbird.autoKey", verifyResult.key); + } keyStatus.auto = { valid: true, expireDate: verifyResult.expire_date, @@ -212,8 +231,14 @@ class HummingbirdProViewProvider { mergedCount: verifyResult.merged_count || 0 }; await this._context.globalState.update("hummingbird.autoExpireDate", verifyResult.expire_date); - } else { - keyStatus.auto = { valid: false, error: verifyResult.error }; + } else if (!verifyResult.isOffline) { + // 后端明确返回无效(非网络错误),自动清除本地 Auto 密钥 + console.warn("[蜂鸟Pro] Auto 密钥已失效,自动清除:", verifyResult.error); + await this._context.globalState.update("hummingbird.autoKey", undefined); + await this._context.globalState.update("hummingbird.autoExpireDate", undefined); + await this._context.globalState.update("hummingbird.autoMergedCount", undefined); + await this._context.globalState.update("hummingbird.key", undefined); + keyStatus.auto = { valid: false, error: verifyResult.error, cleared: true }; } } catch (err) { console.error("[蜂鸟Pro] 检查 Auto 密钥状态失败:", err); @@ -225,6 +250,9 @@ class HummingbirdProViewProvider { try { const verifyResult = await client_1.verifyKey(proKey); if (verifyResult.success && verifyResult.valid) { + if (verifyResult.key && verifyResult.key !== proKey) { + await this._context.globalState.update("hummingbird.proKey", verifyResult.key); + } keyStatus.pro = { valid: true, quota: verifyResult.quota, @@ -234,19 +262,45 @@ class HummingbirdProViewProvider { }; await this._context.globalState.update("hummingbird.proQuota", verifyResult.quota); await this._context.globalState.update("hummingbird.proQuotaUsed", verifyResult.quota_used); - } else { - keyStatus.pro = { valid: false, error: verifyResult.error }; + } else if (!verifyResult.isOffline) { + // 后端明确返回无效(非网络错误),自动清除本地 Pro 密钥 + console.warn("[蜂鸟Pro] Pro 密钥已失效,自动清除:", verifyResult.error); + await this._context.globalState.update("hummingbird.proKey", undefined); + await this._context.globalState.update("hummingbird.proQuota", undefined); + await this._context.globalState.update("hummingbird.proQuotaUsed", undefined); + await this._context.globalState.update("hummingbird.proMergedCount", undefined); + keyStatus.pro = { valid: false, error: verifyResult.error, cleared: true }; } } catch (err) { console.error("[蜂鸟Pro] 检查 Pro 密钥状态失败:", err); } } + // 如果有密钥被清除,同时清理无感换号状态 + if (keyStatus.auto?.cleared || keyStatus.pro?.cleared) { + const remainingAuto = this._context.globalState.get("hummingbird.autoKey"); + const remainingPro = this._context.globalState.get("hummingbird.proKey"); + if (!remainingAuto && !remainingPro) { + // 所有密钥都失效了,清理全部状态 + await this._context.globalState.update("hummingbird.selectedPool", undefined); + await this._context.globalState.update("hummingbird.seamlessInjected", undefined); + await this._context.globalState.update("hummingbird.seamlessCurrentAccount", undefined); + await this._context.globalState.update("hummingbird.expireDate", undefined); + await this._context.globalState.update("hummingbird.switchRemaining", undefined); + extension_1.hideStatusBar(); + } + } + this._postMessage({ 'type': "keyStatusChecked", 'auto': keyStatus.auto, 'pro': keyStatus.pro }); + + // 如果有密钥被清除,重新发送状态给面板刷新UI + if (keyStatus.auto?.cleared || keyStatus.pro?.cleared) { + await this._sendState(); + } } async _handleActivate(key) { try { @@ -262,8 +316,9 @@ class HummingbirdProViewProvider { this._cleanProxySettings(); const verifyResult = await client_1.verifyKey(key); if (verifyResult.success && verifyResult.valid) { - const membershipType = verifyResult.membership_type || 'free'; - const isAuto = membershipType === 'free'; + const membershipType = verifyResult.membership_type || 'auto'; + const isAuto = membershipType === 'auto' || membershipType === 'free'; + const activatedKey = verifyResult.key || key; console.log("[蜂鸟Pro] 激活成功,类型:", membershipType, "后端返回:", { 'expire_date': verifyResult.expire_date, @@ -274,20 +329,25 @@ class HummingbirdProViewProvider { // 根据类型存储到不同字段 if (isAuto) { - await this._context.globalState.update("hummingbird.autoKey", key); + await this._context.globalState.update("hummingbird.autoKey", activatedKey); await this._context.globalState.update("hummingbird.autoExpireDate", verifyResult.expire_date); await this._context.globalState.update("hummingbird.autoMergedCount", verifyResult.merged_count || 0); + // 同步写回旧字段(兼容旧逻辑) + await this._context.globalState.update("hummingbird.key", activatedKey); + await this._context.globalState.update("hummingbird.expireDate", verifyResult.expire_date); } else { - await this._context.globalState.update("hummingbird.proKey", key); + await this._context.globalState.update("hummingbird.proKey", activatedKey); await this._context.globalState.update("hummingbird.proQuota", verifyResult.quota); await this._context.globalState.update("hummingbird.proQuotaUsed", verifyResult.quota_used || 0); await this._context.globalState.update("hummingbird.proMergedCount", verifyResult.merged_count || 0); + // 同步写回旧字段(兼容旧逻辑) + await this._context.globalState.update("hummingbird.key", activatedKey); } this._postMessage({ 'type': "activated", 'success': true, - 'key': key, + 'key': activatedKey, 'membershipType': membershipType, 'expireDate': verifyResult.expire_date, 'quota': verifyResult.quota, @@ -332,6 +392,15 @@ class HummingbirdProViewProvider { const switchResult = await client_1.switchSeamlessToken(savedKey); if (switchResult.switched) { await this._context.globalState.update("hummingbird.switchRemaining", switchResult.switchRemaining); + // 写入新 token 到本地 Cursor 存储 + if (switchResult.data) { + try { + await this._writeAccountToLocal(switchResult.data); + console.log("[蜂鸟Pro] 换号成功,已写入新 token"); + } catch (writeErr) { + console.error("[蜂鸟Pro] 写入 token 失败:", writeErr); + } + } this._postMessage({ 'type': "switched", 'success': true, @@ -365,10 +434,10 @@ class HummingbirdProViewProvider { } async _writeAccountToLocal(accountData) { try { - const condition = process.env.APPDATA || ''; - const joinedPath = path.join(condition, "Cursor", "User", "globalStorage", "state.vscdb"); - const joinedPath1 = path.join(condition, "Cursor", "User", "globalStorage", 'storage.json'); - const joinedPath2 = path.join(condition, "Cursor", "machineid"); + const cursorPaths = account_1.getCursorPaths(); + const joinedPath = cursorPaths.dbPath; + const joinedPath1 = cursorPaths.storagePath; + const joinedPath2 = cursorPaths.machineidPath; if (fs.existsSync(joinedPath)) { const items = []; if (accountData.accessToken) { @@ -377,6 +446,9 @@ class HummingbirdProViewProvider { if (accountData.refreshToken) { items.push(["cursorAuth/refreshToken", accountData.refreshToken]); } + if (accountData.workosSessionToken) { + items.push(["cursorAuth/WorkosCursorSessionToken", accountData.workosSessionToken]); + } if (accountData.email) { items.push(["cursorAuth/cachedEmail", accountData.email]); } @@ -433,6 +505,17 @@ class HummingbirdProViewProvider { await this._context.globalState.update("hummingbird.key", undefined); await this._context.globalState.update("hummingbird.expireDate", undefined); await this._context.globalState.update("hummingbird.switchRemaining", undefined); + // 清理双池相关状态 + await this._context.globalState.update("hummingbird.autoKey", undefined); + await this._context.globalState.update("hummingbird.proKey", undefined); + await this._context.globalState.update("hummingbird.autoExpireDate", undefined); + await this._context.globalState.update("hummingbird.proQuota", undefined); + await this._context.globalState.update("hummingbird.proQuotaUsed", undefined); + await this._context.globalState.update("hummingbird.proMergedCount", undefined); + await this._context.globalState.update("hummingbird.autoMergedCount", undefined); + await this._context.globalState.update("hummingbird.selectedPool", undefined); + await this._context.globalState.update("hummingbird.seamlessInjected", undefined); + await this._context.globalState.update("hummingbird.seamlessCurrentAccount", undefined); extension_1.hideStatusBar(); this._postMessage({ 'type': 'reset', @@ -473,9 +556,9 @@ class HummingbirdProViewProvider { } const hostLine = account_1.getCursorPaths(); const { - dbPath: charIdx, - storagePath: lineItem, - machineidPath: lineIdx + dbPath: dbPath, + storagePath: storagePath, + machineidPath: machineidPath } = hostLine; const module = require("crypto"); const str = module.randomBytes(32).toString("hex"); @@ -484,16 +567,16 @@ class HummingbirdProViewProvider { const result = '{' + module.randomUUID().toUpperCase() + '}'; let count = 0; let items = []; - if (fs.existsSync(lineItem)) { + if (fs.existsSync(storagePath)) { let num = 3; while (num > 0) { try { - const parsed = JSON.parse(fs.readFileSync(lineItem, "utf-8")); + const parsed = JSON.parse(fs.readFileSync(storagePath, "utf-8")); parsed["telemetry.machineId"] = str; parsed["telemetry.macMachineId"] = str1; parsed["telemetry.devDeviceId"] = proxyLine; parsed["telemetry.sqmId"] = result; - fs.writeFileSync(lineItem, JSON.stringify(parsed, null, 4)); + fs.writeFileSync(storagePath, JSON.stringify(parsed, null, 4)); console.log("[蜂鸟Pro] storage.json 已更新"); count++; break; @@ -512,13 +595,13 @@ class HummingbirdProViewProvider { let num = 3; while (num > 0) { try { - const dirPath = path.dirname(lineIdx); + const dirPath = path.dirname(machineidPath); if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { 'recursive': true }); } - fs.writeFileSync(lineIdx, str); + fs.writeFileSync(machineidPath, str); console.log("[蜂鸟Pro] machineid 文件已更新"); count++; break; @@ -533,12 +616,12 @@ class HummingbirdProViewProvider { } } } - if (fs.existsSync(charIdx)) { + if (fs.existsSync(dbPath)) { let num = 3; while (num > 0) { try { const proxyEntry = module.randomUUID(); - const newHostsContent = await sqlite_1.sqliteSetBatch(charIdx, [['storage.serviceMachineId', proxyEntry]]); + const newHostsContent = await sqlite_1.sqliteSetBatch(dbPath, [['storage.serviceMachineId', proxyEntry]]); if (newHostsContent) { console.log("[蜂鸟Pro] SQLite 数据库已更新"); count++; @@ -787,7 +870,7 @@ class HummingbirdProViewProvider { if (lockedInfo) { try { fs.writeFileSync(content1, content, "utf-8"); - remainingCount = true; + isFalse = true; } catch (writeErr2) { console.log("[蜂鸟Pro] Write still failed after permission grant"); } @@ -815,8 +898,8 @@ class HummingbirdProViewProvider { if (process.platform === "darwin") { const pathStr = "/tmp/hosts_cursor_temp"; fs.writeFileSync(pathStr, content, "utf-8"); - const content1 = "do shell script \"cp '" + pathStr + "' '" + content1 + "' && rm '" + pathStr + "' && dscacheutil -flushcache && killall -HUP mDNSResponder\" with administrator privileges"; - await execAsync('osascript -e "' + content1.replace(/"/g, "\\\"") + "\""); + const osaCmd = "do shell script \"cp '" + pathStr + "' '" + content1 + "' && rm '" + pathStr + "' && dscacheutil -flushcache && killall -HUP mDNSResponder\" with administrator privileges"; + await execAsync('osascript -e "' + osaCmd.replace(/"/g, "\\\"") + "\""); } else { fs.writeFileSync(content1, content, "utf-8"); } @@ -830,7 +913,7 @@ class HummingbirdProViewProvider { async _handleToggleProxy(enabled, silent) { try { if (enabled) { - const savedKey = this._context.globalState.get("hummingbird.key"); + const savedKey = this._getActiveKey(); const expireDate = this._context.globalState.get('hummingbird.expireDate'); if (!savedKey) { this._postMessage({ @@ -1012,7 +1095,7 @@ class HummingbirdProViewProvider { if (regOut2 && regOut2.trim()) { const matchResult = regOut2.match(/InstallLocation\s+REG_SZ\s+(.+)/); if (matchResult && matchResult[1] && fs.existsSync(matchResult[1].trim())) { - cursorPath = matchResult[1].trim(); + result = matchResult[1].trim(); } } } catch (e5) { @@ -1048,7 +1131,7 @@ class HummingbirdProViewProvider { for (const str of parts) { const trimmed = str.trim(); if (trimmed && fs.existsSync(trimmed)) { - cursorPath = path.dirname(trimmed); + result = path.dirname(trimmed); break; } } @@ -1091,7 +1174,7 @@ class HummingbirdProViewProvider { if (fileItem && fileItem.trim()) { const matchResult = fileItem.match(/(\/.+\.app)/); if (matchResult) { - cursorPath = matchResult[1]; + result = matchResult[1]; } } } catch (findErr) { @@ -1123,7 +1206,7 @@ class HummingbirdProViewProvider { } = await execAsync("readlink -f /proc/" + condition + "/exe 2>/dev/null"); if (subDir && subDir.trim()) { const trimmed = subDir.trim(); - cursorPath = path.dirname(trimmed); + result = path.dirname(trimmed); if (result.endsWith("/bin")) { result = path.dirname(result); } @@ -1138,9 +1221,9 @@ class HummingbirdProViewProvider { if (subItem && subItem.trim()) { const execResult = await execAsync('readlink -f "' + subItem.trim() + '" 2>/dev/null'); if (execResult.stdout && execResult.stdout.trim()) { - cursorPath = path.dirname(execResult.stdout.trim()); + result = path.dirname(execResult.stdout.trim()); if (result.endsWith('/bin')) { - cursorPath = path.dirname(result); + result = path.dirname(result); } } } @@ -1260,7 +1343,7 @@ class HummingbirdProViewProvider { } async _handleInjectSeamless() { try { - const savedKey = this._context.globalState.get("hummingbird.key"); + const savedKey = this._getActiveKey(); if (!savedKey) { this._postMessage({ 'type': "seamlessInjected", @@ -1269,15 +1352,35 @@ class HummingbirdProViewProvider { }); return; } - const status = await client_1.getUserSwitchStatus(savedKey); - if (!status.valid) { + + // 1. 先调用后端启用无感换号(分配账号) + const deviceId = client_1.getDeviceId(); + console.log("[蜂鸟Pro] 正在调用后端启用无感换号..."); + const enableResult = await client_1.enableSeamless(savedKey, deviceId); + + if (!enableResult.success) { this._postMessage({ 'type': "seamlessInjected", 'success': false, - 'error': status.error || "授权码无效" + 'error': enableResult.error || "启用无感换号失败" }); return; } + + // 2. 验证账号数据 + const accountData = enableResult.data?.account; + if (!accountData || !(accountData.token || accountData.access_token)) { + this._postMessage({ + 'type': "seamlessInjected", + 'success': false, + 'error': "后端未返回账号信息" + }); + return; + } + + console.log("[蜂鸟Pro] 后端分配账号成功:", accountData.email); + + // 3. 获取 workbench 路径并进行本地文件注入 const workbenchPath = await this._getWorkbenchPathAsync(); if (!workbenchPath) { this._postMessage({ @@ -1368,12 +1471,33 @@ class HummingbirdProViewProvider { throw writeErr; } await this._context.globalState.update("hummingbird.seamlessInjected", true); + + // 4. 注入成功后,将后端分配的账号信息写入 Cursor + console.log("[蜂鸟Pro] 正在写入账号信息到 Cursor..."); + try { + await this._writeAccountToLocal({ + accessToken: accountData.access_token || accountData.token, + refreshToken: accountData.refresh_token, + workosSessionToken: accountData.workos_session_token, + email: accountData.email, + membership_type: accountData.membership_type + }); + console.log("[蜂鸟Pro] 账号信息写入成功"); + } catch (writeAccountErr) { + console.error("[蜂鸟Pro] 写入账号信息失败:", writeAccountErr); + // 写入失败不阻塞整个流程,因为注入已经成功 + } + this._postMessage({ 'type': 'seamlessInjected', 'success': true, 'applied': items, 'needRestart': true, - 'message': "无感换号已启用" + 'message': "无感换号已启用", + 'account': { + email: accountData.email, + membership_type: accountData.membership_type + } }); } catch (appDir) { console.error("[蜂鸟Pro] Inject error:", appDir); @@ -1435,6 +1559,17 @@ class HummingbirdProViewProvider { } throw writeErr; } + + // 通知后端释放锁定的账号 + try { + const savedKey = this._context.globalState.get("hummingbird.key"); + if (savedKey) { + await client_1.disableSeamless(savedKey); + } + } catch (disableErr) { + console.warn("[蜂鸟Pro] 通知后端禁用无感换号失败:", disableErr); + } + this._postMessage({ 'type': "seamlessRestored", 'success': true, @@ -1480,7 +1615,7 @@ class HummingbirdProViewProvider { } async _handleGetUserSwitchStatus() { try { - const savedKey = this._context.globalState.get('hummingbird.key'); + const savedKey = this._getActiveKey(); if (!savedKey) { this._postMessage({ 'type': "userSwitchStatus", @@ -1713,7 +1848,7 @@ class HummingbirdProViewProvider { } async _handleCheckUsageBeforeSwitch(silent) { try { - const savedKey = this._context.globalState.get("hummingbird.key"); + const savedKey = this._getActiveKey(); if (!savedKey) { this._postMessage({ 'type': "usageCheckResult", @@ -2129,4 +2264,4 @@ class HummingbirdProViewProvider { // 以下为占位注释,保持文件结构不变 exports.HummingbirdProViewProvider = HummingbirdProViewProvider; -HummingbirdProViewProvider.CURRENT_VERSION = '2.0.0'; \ No newline at end of file +HummingbirdProViewProvider.CURRENT_VERSION = '2.1.0';