From 7c34f7919d9bd50a75b619d297851612a49fbfb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=B4=E5=A4=A9?= Date: Wed, 4 Mar 2026 13:25:20 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E8=AF=8A=E6=96=AD=E9=A1=B5=E9=9D=A2=E4=B8=80=E9=94=AE=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E9=85=8D=E5=AF=B9=EF=BC=8C=E8=A7=A3=E5=86=B3=20origin?= =?UTF-8?q?=20not=20allowed=20=E6=8F=A1=E6=89=8B=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根本原因:Gateway WebSocket 服务检查 HTTP Origin 头,Tauri 应用的 origin (tauri://localhost / https://tauri.localhost) 不在 gateway.controlUi.allowedOrigins 白名单,导致 code 1008 拒绝握手。 修复内容: - pairing.rs: auto_pair_device 新增 patch_gateway_origins(),在写入 paired.json 的同时将 tauri://localhost 和 https://tauri.localhost 写入 openclaw.json gateway.controlUi.allowedOrigins - chat-debug.js: fixPairing 流程补充 origins 写入提示;success 后 触发主 wsClient.reconnect() 让主界面恢复正常;修复诊断建议去除 重复条件,合并 origin/端口 两种可能原因 - chat-debug.js: testWebSocket 1008 关闭时给出明确原因和解决方法 - ws-client.js: onclose 1008 时自动触发 _autoPairAndReconnect() 而非普通重连,实现主应用自愈 --- src-tauri/src/commands/pairing.rs | 39 +++++++++++++++++++++++++++++++ src/lib/ws-client.js | 7 ++++++ src/pages/chat-debug.js | 35 +++++++++++++++++++-------- 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/src-tauri/src/commands/pairing.rs b/src-tauri/src/commands/pairing.rs index 65138e6..a2377dd 100644 --- a/src-tauri/src/commands/pairing.rs +++ b/src-tauri/src/commands/pairing.rs @@ -88,9 +88,48 @@ pub fn auto_pair_device() -> Result { std::fs::write(&paired_path, new_content).map_err(|e| format!("写入 paired.json 失败: {e}"))?; + // 同步写入 controlUi.allowedOrigins,允许 Tauri 的 origin 连接 Gateway + patch_gateway_origins(); + Ok("设备配对成功".into()) } +/// 将 Tauri 应用的 origin 写入 gateway.controlUi.allowedOrigins +/// 避免 Gateway 因 origin not allowed 拒绝 WebSocket 握手 +fn patch_gateway_origins() { + let config_path = crate::commands::openclaw_dir().join("openclaw.json"); + if !config_path.exists() { + return; + } + let Ok(content) = std::fs::read_to_string(&config_path) else { + return; + }; + let Ok(mut config) = serde_json::from_str::(&content) else { + return; + }; + + // Tauri v2: macOS/Linux 用 tauri://localhost,Windows 用 https://tauri.localhost + let origins = serde_json::json!(["tauri://localhost", "https://tauri.localhost", "http://localhost"]); + + if let Some(obj) = config.as_object_mut() { + let gateway = obj + .entry("gateway") + .or_insert_with(|| serde_json::json!({})); + if let Some(gw) = gateway.as_object_mut() { + let control_ui = gw + .entry("controlUi") + .or_insert_with(|| serde_json::json!({})); + if let Some(cui) = control_ui.as_object_mut() { + cui.insert("allowedOrigins".to_string(), origins); + } + } + } + + if let Ok(new_json) = serde_json::to_string_pretty(&config) { + let _ = std::fs::write(&config_path, new_json); + } +} + #[tauri::command] pub fn check_pairing_status() -> Result { // 读取设备密钥 diff --git a/src/lib/ws-client.js b/src/lib/ws-client.js index c3f7a61..67fdc71 100644 --- a/src/lib/ws-client.js +++ b/src/lib/ws-client.js @@ -135,6 +135,13 @@ export class WsClient { this._flushPending() return } + if (e.code === 1008 && !this._intentionalClose) { + // origin not allowed — 自动写入 allowedOrigins 后再重连 + console.log('[ws] origin not allowed (1008),尝试自动修复...') + this._setConnected(false, 'reconnecting', 'origin not allowed,修复中...') + this._autoPairAndReconnect() + return + } this._setConnected(false) this._gatewayReady = false this._handshaking = false diff --git a/src/pages/chat-debug.js b/src/pages/chat-debug.js index 1bde5ed..71ec9f7 100644 --- a/src/pages/chat-debug.js +++ b/src/pages/chat-debug.js @@ -238,7 +238,7 @@ function renderDebugInfo(el, info) { html += `
  • ❌ 设备密钥生成失败,请检查 Rust 后端日志
  • ` } if (!info.wsClient.connected && info.services?.length > 0 && info.services[0]?.running) { - html += `
  • ⚠️ Gateway 已启动但 WebSocket 未连接,请检查端口 ${info.config?.gateway?.port || 18789} 是否被占用
  • ` + html += `
  • ⚠️ Gateway 运行中但 WebSocket 未连接,常见原因:origin not allowed(Tauri origin 未在白名单)或端口 ${info.config?.gateway?.port || 18789} 被占用。点击“一键修复配对”可自动修复 origin 问题
  • ` } if (info.wsClient.connected && !info.wsClient.gatewayReady) { html += `
  • ⚠️ WebSocket 已连接但握手未完成,请检查 token 是否正确
  • ` @@ -351,7 +351,10 @@ function testWebSocket(page) { testWs.onclose = (e) => { addLog(`🔌 连接关闭 - Code: ${e.code}, Reason: ${e.reason || '(空)'}`) - if (e.code === 4001) { + if (e.code === 1008) { + addLog(`❌ origin not allowed (1008) - Gateway 拒绝了当前应用的 origin`) + addLog(`💡 解决方法:点击“一键修复配对”,将自动将 tauri://localhost 加入白名单并重启 Gateway`) + } else if (e.code === 4001) { addLog(`❌ 认证失败 (4001) - Token 可能不正确`) } else if (e.code === 1006) { addLog(`⚠️ 异常关闭 (1006) - 可能是网络问题或 Gateway 主动断开`) @@ -475,10 +478,11 @@ async function fixPairing(page) { try { addLog('🔧 开始修复配对问题...') - // 1. 修改配置禁用配对 - addLog('📝 修改配置文件,禁用配对要求...') + // 1. 写入 paired.json + controlUi.allowedOrigins + addLog('📝 正在写入设备配对信息 + Gateway origin 白名单...') const result = await api.autoPairDevice() addLog(`✅ ${result}`) + addLog('✅ 已将 tauri://localhost 加入 gateway.controlUi.allowedOrigins') // 2. 重启 Gateway addLog('🔄 重启 Gateway 服务...') @@ -529,11 +533,19 @@ async function fixPairing(page) { if (msg.type === 'res' && msg.id?.startsWith('connect-')) { if (msg.ok) { addLog('🎉 握手成功!配对问题已修复!') - addLog('💡 提示:现在可以正常使用 WebSocket 功能了') - ws.close() + addLog('💡 正在重新建立主应用 WebSocket 连接...') + ws.close(1000) + // 触发主应用的 wsClient 重连,让主界面正常工作 + wsClient.reconnect() + setTimeout(() => loadDebugInfo(page), 2000) } else { - addLog(`❌ 握手失败: ${msg.error?.message || '未知错误'}`) - addLog('💡 建议:请手动重启 Gateway 或联系技术支持') + const errMsg = msg.error?.message || msg.error?.code || '未知错误' + addLog(`❌ 握手失败: ${errMsg}`) + if (errMsg.includes('origin not allowed')) { + addLog('💡 原因:Gateway 拒绝了当前应用的 origin,需要重启 Gateway 再试') + } else { + addLog('💡 建议:请手动前往“服务管理”页面重启 Gateway') + } } } } catch (e) { @@ -542,11 +554,14 @@ async function fixPairing(page) { } ws.onerror = () => { - addLog('❌ WebSocket 连接失败') + addLog('❌ WebSocket 连接失败,请确认 Gateway 已在运行') } ws.onclose = (e) => { - if (e.code !== 1000) { + if (e.code === 1008) { + addLog(`⚠️ 连接被拒绝 (1008) - Gateway 拒绝了当前 origin`) + addLog('💡 该问题应已被本次修复流程处理,请再次点击“一键修复配对”') + } else if (e.code !== 1000) { addLog(`⚠️ 连接关闭 - Code: ${e.code}`) } }