mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-27 19:30:15 +08:00
172 lines
5.6 KiB
Rust
172 lines
5.6 KiB
Rust
/// 设备配对命令
|
||
/// 自动向 Gateway 注册设备,跳过手动配对流程
|
||
|
||
#[tauri::command]
|
||
pub fn auto_pair_device() -> Result<String, String> {
|
||
// 读取设备密钥
|
||
let device_key_path = crate::commands::openclaw_dir().join("clawpanel-device-key.json");
|
||
if !device_key_path.exists() {
|
||
return Err("设备密钥文件不存在".into());
|
||
}
|
||
|
||
let device_key_content =
|
||
std::fs::read_to_string(&device_key_path).map_err(|e| format!("读取设备密钥失败: {e}"))?;
|
||
|
||
let device_key: serde_json::Value =
|
||
serde_json::from_str(&device_key_content).map_err(|e| format!("解析设备密钥失败: {e}"))?;
|
||
|
||
let device_id = device_key["deviceId"]
|
||
.as_str()
|
||
.ok_or("设备 ID 不存在")?
|
||
.to_string();
|
||
|
||
let public_key = device_key["publicKey"]
|
||
.as_str()
|
||
.ok_or("公钥不存在")?
|
||
.to_string();
|
||
|
||
// 读取或创建 paired.json
|
||
let paired_path = crate::commands::openclaw_dir()
|
||
.join("devices")
|
||
.join("paired.json");
|
||
let devices_dir = crate::commands::openclaw_dir().join("devices");
|
||
|
||
// 确保 devices 目录存在
|
||
if !devices_dir.exists() {
|
||
std::fs::create_dir_all(&devices_dir).map_err(|e| format!("创建 devices 目录失败: {e}"))?;
|
||
}
|
||
|
||
let mut paired: serde_json::Value = if paired_path.exists() {
|
||
let content = std::fs::read_to_string(&paired_path)
|
||
.map_err(|e| format!("读取 paired.json 失败: {e}"))?;
|
||
serde_json::from_str(&content).map_err(|e| format!("解析 paired.json 失败: {e}"))?
|
||
} else {
|
||
serde_json::json!({})
|
||
};
|
||
|
||
// 无论是否已配对,都确保 gateway.controlUi.allowedOrigins 已写入
|
||
patch_gateway_origins();
|
||
|
||
// 检查设备是否已配对
|
||
if paired.get(&device_id).is_some() {
|
||
return Ok("设备已配对".into());
|
||
}
|
||
|
||
// 添加设备到配对列表
|
||
let now_ms = std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_millis() as u64;
|
||
|
||
paired[&device_id] = serde_json::json!({
|
||
"deviceId": device_id,
|
||
"publicKey": public_key,
|
||
"platform": "desktop",
|
||
"clientId": "gateway-client",
|
||
"clientMode": "backend",
|
||
"role": "operator",
|
||
"roles": ["operator"],
|
||
"scopes": [
|
||
"operator.admin",
|
||
"operator.approvals",
|
||
"operator.pairing",
|
||
"operator.read",
|
||
"operator.write"
|
||
],
|
||
"approvedScopes": [
|
||
"operator.admin",
|
||
"operator.approvals",
|
||
"operator.pairing",
|
||
"operator.read",
|
||
"operator.write"
|
||
],
|
||
"tokens": {},
|
||
"createdAtMs": now_ms,
|
||
"approvedAtMs": now_ms
|
||
});
|
||
|
||
// 写入 paired.json
|
||
let new_content = serde_json::to_string_pretty(&paired)
|
||
.map_err(|e| format!("序列化 paired.json 失败: {e}"))?;
|
||
|
||
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::<serde_json::Value>(&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<bool, String> {
|
||
// 读取设备密钥
|
||
let device_key_path = crate::commands::openclaw_dir().join("clawpanel-device-key.json");
|
||
if !device_key_path.exists() {
|
||
return Ok(false);
|
||
}
|
||
|
||
let device_key_content =
|
||
std::fs::read_to_string(&device_key_path).map_err(|e| format!("读取设备密钥失败: {e}"))?;
|
||
|
||
let device_key: serde_json::Value =
|
||
serde_json::from_str(&device_key_content).map_err(|e| format!("解析设备密钥失败: {e}"))?;
|
||
|
||
let device_id = device_key["deviceId"].as_str().ok_or("设备 ID 不存在")?;
|
||
|
||
// 检查 paired.json
|
||
let paired_path = crate::commands::openclaw_dir()
|
||
.join("devices")
|
||
.join("paired.json");
|
||
if !paired_path.exists() {
|
||
return Ok(false);
|
||
}
|
||
|
||
let content =
|
||
std::fs::read_to_string(&paired_path).map_err(|e| format!("读取 paired.json 失败: {e}"))?;
|
||
|
||
let paired: serde_json::Value =
|
||
serde_json::from_str(&content).map_err(|e| format!("解析 paired.json 失败: {e}"))?;
|
||
|
||
Ok(paired.get(device_id).is_some())
|
||
}
|