Skip to content

Commit 983d400

Browse files
committed
feat: add eventbus-backed RPA tab commands
1 parent 6feefe7 commit 983d400

4 files changed

Lines changed: 212 additions & 5 deletions

File tree

‎src/infrastructure/eventbus/manager.rs‎

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
use super::connection::{BrowserConnection, MessageHandler};
22
use super::error::{EventBusError, Result};
33
use super::global::EnvConnectionPayload;
4-
use super::message::Message;
4+
use super::message::{Message, MessageType};
55
use super::topics::Topic;
66
use super::transport::PipeServer;
77
use crate::infrastructure::diagnostics::{log_error, log_info, log_warn};
88
use serde::{Deserialize, Serialize};
99
use std::collections::HashMap;
1010
use std::sync::Arc;
11-
use tokio::sync::{RwLock, mpsc};
11+
use tokio::sync::{RwLock, mpsc, oneshot};
1212

1313
#[derive(Debug, Clone, Serialize, Deserialize)]
1414
pub struct FingerprintConfig {
@@ -110,6 +110,7 @@ type AuthInfoProvider = Arc<dyn Fn() -> AuthInfo + Send + Sync>;
110110

111111
pub struct EventBusManager {
112112
connections: Arc<RwLock<HashMap<String, Arc<BrowserConnection>>>>,
113+
pending_requests: Arc<RwLock<HashMap<String, HashMap<u32, oneshot::Sender<Message>>>>>,
113114
message_handler: Arc<RwLock<Option<MessageHandler>>>,
114115
disconnect_handler: Arc<RwLock<Option<DisconnectHandler>>>,
115116
connection_status_handler: Arc<RwLock<Option<ConnectionStatusHandler>>>,
@@ -122,6 +123,7 @@ impl EventBusManager {
122123
pub fn new() -> Self {
123124
Self {
124125
connections: Arc::new(RwLock::new(HashMap::new())),
126+
pending_requests: Arc::new(RwLock::new(HashMap::new())),
125127
message_handler: Arc::new(RwLock::new(None)),
126128
disconnect_handler: Arc::new(RwLock::new(None)),
127129
connection_status_handler: Arc::new(RwLock::new(None)),
@@ -297,6 +299,7 @@ impl EventBusManager {
297299
let handler = self.message_handler.read().await.clone();
298300
let disconnect_handler = self.disconnect_handler.read().await.clone();
299301
let connections = self.connections.clone();
302+
let pending_requests = self.pending_requests.clone();
300303
let status_handler = self.connection_status_handler.clone();
301304

302305
if let Some(handler) = handler {
@@ -305,6 +308,25 @@ impl EventBusManager {
305308
loop {
306309
match browser_connection.recv().await {
307310
Ok(msg) => {
311+
if msg.msg_type == MessageType::Response {
312+
let mut pending = pending_requests.write().await;
313+
if let Some(waiters) = pending.get_mut(&env_id_for_loop) {
314+
if let Some(sender) = waiters.remove(&msg.msg_id) {
315+
let _ = sender.send(msg);
316+
continue;
317+
}
318+
}
319+
320+
log_warn(
321+
"eventbus",
322+
format!(
323+
"[{}] Received unexpected response message: {}",
324+
env_id_for_loop, msg.msg_id
325+
),
326+
);
327+
continue;
328+
}
329+
308330
if msg.topic == Topic::AuthRequest {
309331
if let Err(error) =
310332
Self::handle_auth_request(&browser_connection, msg).await
@@ -333,6 +355,11 @@ impl EventBusManager {
333355
guard.remove(&env_id_for_loop);
334356
}
335357

358+
{
359+
let mut pending = pending_requests.write().await;
360+
pending.remove(&env_id_for_loop);
361+
}
362+
336363
{
337364
let status = status_handler.read().await.clone();
338365
if let Some(status) = status {
@@ -393,7 +420,32 @@ impl EventBusManager {
393420
.clone();
394421
drop(connections);
395422

396-
connection.send_request(topic, data).await
423+
let message = Message::request(topic, data);
424+
let msg_id = message.msg_id;
425+
let (tx, rx) = oneshot::channel();
426+
427+
{
428+
let mut pending = self.pending_requests.write().await;
429+
pending
430+
.entry(env_id.to_string())
431+
.or_default()
432+
.insert(msg_id, tx);
433+
}
434+
435+
if let Err(error) = connection.send(&message).await {
436+
let mut pending = self.pending_requests.write().await;
437+
if let Some(waiters) = pending.get_mut(env_id) {
438+
waiters.remove(&msg_id);
439+
}
440+
return Err(error);
441+
}
442+
443+
rx.await.map_err(|error| {
444+
EventBusError::ReceiveFailed(format!(
445+
"failed to receive response for env {} msg {}: {}",
446+
env_id, msg_id, error
447+
))
448+
})
397449
}
398450

399451
pub async fn broadcast(&self, topic: Topic, data: Vec<u8>) -> Vec<(String, Result<()>)> {
@@ -450,9 +502,11 @@ impl EventBusManager {
450502

451503
pub async fn disconnect_all(&self) {
452504
let mut connections = self.connections.write().await;
505+
let mut pending_requests = self.pending_requests.write().await;
453506
for (env_id, connection) in connections.drain() {
454507
let message = Message::event(Topic::Disconnect, vec![]);
455508
let _ = connection.send(&message).await;
509+
pending_requests.remove(&env_id);
456510
log_info("eventbus", format!("[{}] Disconnected", env_id));
457511
}
458512
}

‎src/services/environment/kernel/launcher.rs‎

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use super::cdp::CdpEndpointManager;
22
use super::job::JobManager;
33
use super::types::{
4-
BatchLaunchResult, CdpEndpointResponse, EnvironmentStartRequest, WindowBoundsRequest,
4+
BatchLaunchResult, CdpEndpointResponse, EnvironmentStartRequest, RpaTabCloseResult,
5+
RpaTabSelection, RpaTabsSnapshot, WindowBoundsRequest,
56
};
67
use crate::app::{EventPublisher, Result, RuntimeError};
78
use crate::infrastructure::diagnostics::log_info;
@@ -11,6 +12,13 @@ use crate::services::environment::{EnvironmentStatus, EnvironmentStatusManager};
1112
use std::path::Path;
1213
use std::sync::Arc;
1314

15+
#[derive(serde::Serialize)]
16+
struct RpaCommandPayload<'a> {
17+
action: &'a str,
18+
#[serde(skip_serializing_if = "Option::is_none")]
19+
position: Option<u32>,
20+
}
21+
1422
pub async fn launch_browser(
1523
request: EnvironmentStartRequest,
1624
cdp_endpoint_manager: Arc<CdpEndpointManager>,
@@ -299,6 +307,59 @@ pub async fn get_cdp_endpoint(
299307
}))
300308
}
301309

310+
pub async fn list_rpa_tabs(env_uuid: String) -> Result<RpaTabsSnapshot> {
311+
let env_id = env_uuid.trim().to_string();
312+
let manager = eventbus_manager();
313+
314+
if !manager.is_connected(&env_id).await {
315+
return Err(RuntimeError::Internal(format!("环境 {} 未连接", env_id)));
316+
}
317+
318+
let response = manager
319+
.send_request(&env_id, Topic::RpaCommand, encode_rpa_command("list_tabs", None)?)
320+
.await?;
321+
322+
decode_rpa_response::<RpaTabsSnapshot>(response)
323+
}
324+
325+
pub async fn select_rpa_tab(env_uuid: String, position: u32) -> Result<RpaTabSelection> {
326+
let env_id = env_uuid.trim().to_string();
327+
let manager = eventbus_manager();
328+
329+
if !manager.is_connected(&env_id).await {
330+
return Err(RuntimeError::Internal(format!("环境 {} 未连接", env_id)));
331+
}
332+
333+
let response = manager
334+
.send_request(
335+
&env_id,
336+
Topic::RpaCommand,
337+
encode_rpa_command("select_tab", Some(position))?,
338+
)
339+
.await?;
340+
341+
decode_rpa_response::<RpaTabSelection>(response)
342+
}
343+
344+
pub async fn close_rpa_tab(env_uuid: String, position: u32) -> Result<RpaTabCloseResult> {
345+
let env_id = env_uuid.trim().to_string();
346+
let manager = eventbus_manager();
347+
348+
if !manager.is_connected(&env_id).await {
349+
return Err(RuntimeError::Internal(format!("环境 {} 未连接", env_id)));
350+
}
351+
352+
let response = manager
353+
.send_request(
354+
&env_id,
355+
Topic::RpaCommand,
356+
encode_rpa_command("close_tab", Some(position))?,
357+
)
358+
.await?;
359+
360+
decode_rpa_response::<RpaTabCloseResult>(response)
361+
}
362+
302363
pub async fn batch_launch_environments(
303364
requests: Vec<EnvironmentStartRequest>,
304365
cdp_endpoint_manager: Arc<CdpEndpointManager>,
@@ -361,6 +422,36 @@ fn encode_window_bounds_payload(x: i32, y: i32, width: i32, height: i32) -> Resu
361422
Ok(payload)
362423
}
363424

425+
fn encode_rpa_command(action: &str, position: Option<u32>) -> Result<Vec<u8>> {
426+
serde_json::to_vec(&RpaCommandPayload { action, position })
427+
.map_err(|error| RuntimeError::Serialization(error.to_string()))
428+
}
429+
430+
fn decode_rpa_response<T>(response: Message) -> Result<T>
431+
where
432+
T: serde::de::DeserializeOwned,
433+
{
434+
if response.error_code != 0 {
435+
return Err(RuntimeError::Internal(read_rpa_error_message(&response.data)));
436+
}
437+
438+
serde_json::from_slice(&response.data)
439+
.map_err(|error| RuntimeError::Serialization(error.to_string()))
440+
}
441+
442+
fn read_rpa_error_message(data: &[u8]) -> String {
443+
#[derive(serde::Deserialize)]
444+
struct ErrorPayload {
445+
message: Option<String>,
446+
}
447+
448+
serde_json::from_slice::<ErrorPayload>(data)
449+
.ok()
450+
.and_then(|payload| payload.message)
451+
.filter(|message| !message.trim().is_empty())
452+
.unwrap_or_else(|| "RPA_COMMAND_FAILED".to_string())
453+
}
454+
364455
#[cfg(test)]
365456
mod tests {
366457
use super::encode_window_bounds_payload;

‎src/services/environment/kernel/mod.rs‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ use cdp::CdpEndpointManager;
99
use job::JobManager;
1010
use launcher::{
1111
batch_launch_environments, batch_stop_environments, get_cdp_endpoint,
12-
get_connected_environments, launch_browser, refresh_proxy, set_window_bounds, stop_environment,
12+
get_connected_environments, launch_browser, list_rpa_tabs, refresh_proxy, close_rpa_tab,
13+
select_rpa_tab, set_window_bounds, stop_environment,
1314
};
1415
use std::sync::Arc;
1516
use types::{EnvironmentCommandRequest, EnvironmentCommandResponse};
@@ -96,6 +97,18 @@ impl KernelRuntime {
9697
get_cdp_endpoint(env_uuid, self.cdp_endpoint_manager.clone()).await?;
9798
Ok(EnvironmentCommandResponse::CdpEndpoint { endpoint })
9899
}
100+
EnvironmentCommandRequest::ListRpaTabs { env_uuid } => {
101+
let snapshot = list_rpa_tabs(env_uuid).await?;
102+
Ok(EnvironmentCommandResponse::RpaTabsSnapshot { snapshot })
103+
}
104+
EnvironmentCommandRequest::SelectRpaTab { env_uuid, position } => {
105+
let selection = select_rpa_tab(env_uuid, position).await?;
106+
Ok(EnvironmentCommandResponse::RpaTabSelected { selection })
107+
}
108+
EnvironmentCommandRequest::CloseRpaTab { env_uuid, position } => {
109+
let result = close_rpa_tab(env_uuid, position).await?;
110+
Ok(EnvironmentCommandResponse::RpaTabClosed { result })
111+
}
99112
EnvironmentCommandRequest::GetEnvironmentStatus { env_uuid } => {
100113
let status = self.status_manager.get_status(&env_uuid).await;
101114
Ok(EnvironmentCommandResponse::Status { status })

‎src/services/environment/kernel/types.rs‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,35 @@ pub struct WindowBoundsRequest {
6262
pub height: i32,
6363
}
6464

65+
#[derive(Debug, Clone, Serialize, Deserialize)]
66+
pub struct RpaTabInfo {
67+
pub position: u32,
68+
pub title: String,
69+
pub url: String,
70+
pub active: bool,
71+
pub target_id: String,
72+
}
73+
74+
#[derive(Debug, Clone, Serialize, Deserialize)]
75+
pub struct RpaTabsSnapshot {
76+
pub tabs: Vec<RpaTabInfo>,
77+
pub active_position: Option<u32>,
78+
pub total: u32,
79+
}
80+
81+
#[derive(Debug, Clone, Serialize, Deserialize)]
82+
pub struct RpaTabSelection {
83+
pub position: u32,
84+
pub target_id: String,
85+
}
86+
87+
#[derive(Debug, Clone, Serialize, Deserialize)]
88+
pub struct RpaTabCloseResult {
89+
pub closed_position: u32,
90+
pub active_position: u32,
91+
pub target_id: String,
92+
}
93+
6594
#[derive(Debug, Clone, Serialize, Deserialize)]
6695
#[serde(tag = "command", rename_all = "snake_case")]
6796
pub enum EnvironmentCommandRequest {
@@ -88,6 +117,17 @@ pub enum EnvironmentCommandRequest {
88117
GetCdpEndpoint {
89118
env_uuid: String,
90119
},
120+
ListRpaTabs {
121+
env_uuid: String,
122+
},
123+
SelectRpaTab {
124+
env_uuid: String,
125+
position: u32,
126+
},
127+
CloseRpaTab {
128+
env_uuid: String,
129+
position: u32,
130+
},
91131
GetEnvironmentStatus {
92132
env_uuid: String,
93133
},
@@ -107,6 +147,15 @@ pub enum EnvironmentCommandResponse {
107147
CdpEndpoint {
108148
endpoint: Option<CdpEndpointResponse>,
109149
},
150+
RpaTabsSnapshot {
151+
snapshot: RpaTabsSnapshot,
152+
},
153+
RpaTabSelected {
154+
selection: RpaTabSelection,
155+
},
156+
RpaTabClosed {
157+
result: RpaTabCloseResult,
158+
},
110159
BatchLaunchResults {
111160
results: Vec<BatchLaunchResult>,
112161
},

0 commit comments

Comments
 (0)