playwright_cdp/cdp/
connection.rs1use crate::cdp::messages::{CdpEvent, CdpRequest, Message};
8use crate::cdp::transport::{self, WebSocketWriter};
9use crate::{Error, Result};
10use parking_lot::Mutex;
11use serde_json::Value;
12use std::collections::HashMap;
13use std::sync::atomic::{AtomicU32, Ordering};
14use std::sync::Arc;
15use tokio::sync::{broadcast, mpsc, oneshot};
16
17const EVENT_CHANNEL_CAPACITY: usize = 128;
19
20type SessionKey = Option<String>;
22
23pub struct CdpConnection {
29 next_id: AtomicU32,
30 callbacks: Mutex<HashMap<u32, oneshot::Sender<Result<Value>>>>,
31 event_channels: Mutex<HashMap<SessionKey, broadcast::Sender<CdpEvent>>>,
32 writer: tokio::sync::Mutex<WebSocketWriter>,
33}
34
35impl CdpConnection {
36 pub async fn connect(
41 ws_url: &str,
42 headers: Option<HashMap<String, String>>,
43 ) -> Result<(Arc<Self>, broadcast::Receiver<CdpEvent>)> {
44 let (writer, message_rx) = transport::connect(ws_url, headers).await?;
45
46 let conn = Arc::new(Self {
47 next_id: AtomicU32::new(1),
48 callbacks: Mutex::new(HashMap::new()),
49 event_channels: Mutex::new(HashMap::new()),
50 writer: tokio::sync::Mutex::new(writer),
51 });
52
53 let browser_rx = conn.subscribe(None);
54
55 let conn_for_loop = Arc::clone(&conn);
56 tokio::spawn(async move {
57 conn_for_loop.dispatch_loop(message_rx).await;
58 });
59
60 Ok((conn, browser_rx))
61 }
62
63 pub async fn send_command(
66 self: &Arc<Self>,
67 session_id: Option<&str>,
68 method: &str,
69 params: Value,
70 ) -> Result<Value> {
71 let id = self.next_id.fetch_add(1, Ordering::SeqCst);
72 let (tx, rx) = oneshot::channel::<Result<Value>>();
73 self.callbacks.lock().insert(id, tx);
74
75 let request = CdpRequest {
76 id,
77 method: method.to_string(),
78 params,
79 session_id: session_id.map(|s| s.to_string()),
80 };
81 let value = serde_json::to_value(&request)?;
82
83 let send_result = self.writer.lock().await.send(&value).await;
85 if let Err(e) = send_result {
86 self.callbacks.lock().remove(&id);
87 return Err(e);
88 }
89
90 match rx.await {
91 Ok(res) => res,
92 Err(_) => {
93 self.callbacks.lock().remove(&id);
94 Err(Error::ChannelClosed)
95 }
96 }
97 }
98
99 pub fn subscribe(&self, session_id: SessionKey) -> broadcast::Receiver<CdpEvent> {
103 self.event_channels
104 .lock()
105 .entry(session_id)
106 .or_insert_with(|| broadcast::channel(EVENT_CHANNEL_CAPACITY).0)
107 .subscribe()
108 }
109
110 pub async fn close(&self) -> Result<()> {
112 self.writer.lock().await.close().await
113 }
114
115 async fn dispatch_loop(self: Arc<Self>, mut message_rx: mpsc::Receiver<Value>) {
116 while let Some(v) = message_rx.recv().await {
117 match serde_json::from_value::<Message>(v) {
118 Ok(Message::Response(r)) => {
119 let result = match r.error {
120 Some(e) => Err(Error::CdpError {
121 code: e.code,
122 message: e.message,
123 data: e.data,
124 }),
125 None => Ok(r.result.unwrap_or(Value::Null)),
126 };
127 if let Some(cb) = self.callbacks.lock().remove(&r.id) {
128 let _ = cb.send(result);
129 } else {
130 tracing::trace!(id = r.id, "orphan CDP response");
131 }
132 }
133 Ok(Message::Event(e)) => {
134 let key: SessionKey = e.session_id.clone();
135 let channel = self
136 .event_channels
137 .lock()
138 .entry(key)
139 .or_insert_with(|| broadcast::channel(EVENT_CHANNEL_CAPACITY).0)
140 .clone();
141 let _ = channel.send(e);
144 }
145 Err(err) => {
146 tracing::error!(error = %err, "failed to parse CDP message");
147 }
148 }
149 }
150
151 tracing::debug!("CDP transport closed; failing pending callbacks");
152 self.callbacks.lock().clear();
154 }
155}
156
157