drission/cdp/
websocket.rs1use std::collections::{HashMap, VecDeque};
7use std::sync::Arc;
8use std::time::Duration;
9
10use serde_json::{Value, json};
11use tokio::sync::Mutex;
12use tokio::sync::broadcast::error::RecvError;
13use tokio::time::{Instant, sleep};
14
15use crate::Result;
16use crate::cdp::core::{CdpCore, EventBuf};
17use crate::protocol::Connection;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum WsDirection {
22 Sent,
23 Received,
24}
25
26#[derive(Debug, Clone)]
28pub struct WsMessage {
29 pub direction: WsDirection,
30 pub url: String,
31 pub opcode: u8,
32 pub data: String,
34}
35
36impl WsMessage {
37 pub fn is_text(&self) -> bool {
38 self.opcode == 1
39 }
40 pub fn is_binary(&self) -> bool {
41 self.opcode == 2
42 }
43 pub fn text(&self) -> String {
45 if self.is_text() {
46 self.data.clone()
47 } else {
48 crate::util::base64_decode(&self.data)
49 .and_then(|b| String::from_utf8(b).ok())
50 .unwrap_or_default()
51 }
52 }
53 pub fn bytes(&self) -> Vec<u8> {
55 if self.is_text() {
56 self.data.clone().into_bytes()
57 } else {
58 crate::util::base64_decode(&self.data).unwrap_or_default()
59 }
60 }
61 pub fn json(&self) -> Value {
63 serde_json::from_str(&self.text()).unwrap_or(Value::Null)
64 }
65}
66
67#[derive(Debug, Clone, Default)]
69pub struct WsFilter {
70 pub url_contains: Option<String>,
71 pub direction: Option<WsDirection>,
72 pub with_control: bool,
74}
75
76impl WsFilter {
77 pub fn new() -> Self {
78 Self::default()
79 }
80 pub fn url_contains(mut self, s: impl Into<String>) -> Self {
81 self.url_contains = Some(s.into());
82 self
83 }
84 pub fn direction(mut self, d: WsDirection) -> Self {
85 self.direction = Some(d);
86 self
87 }
88 pub fn with_control(mut self) -> Self {
89 self.with_control = true;
90 self
91 }
92 fn matches(&self, m: &WsMessage) -> bool {
93 if !(self.with_control || m.opcode == 1 || m.opcode == 2) {
94 return false;
95 }
96 if let Some(s) = &self.url_contains {
97 if !m.url.contains(s) {
98 return false;
99 }
100 }
101 if let Some(d) = self.direction {
102 if m.direction != d {
103 return false;
104 }
105 }
106 true
107 }
108}
109
110const BUFFER_CAP: usize = 500;
111
112pub struct ChromiumWsListener {
114 core: Arc<CdpCore>,
115}
116
117impl ChromiumWsListener {
118 pub(crate) fn new(core: Arc<CdpCore>) -> Self {
119 Self { core }
120 }
121
122 pub async fn start(&self) -> Result<()> {
124 self.start_with(WsFilter::default()).await
125 }
126
127 pub async fn start_with(&self, filter: WsFilter) -> Result<()> {
129 self.stop().await?;
130 self.core.send("Network.enable", json!({})).await?;
131 let buf = self.core.ws.lock().await.buf.clone();
132 let task = tokio::spawn(ws_pump(
133 self.core.conn.clone(),
134 self.core.session_id.clone(),
135 filter,
136 buf,
137 ));
138 let mut g = self.core.ws.lock().await;
139 g.running = true;
140 g.abort = Some(task.abort_handle());
141 Ok(())
142 }
143
144 pub async fn listening(&self) -> bool {
146 self.core.ws.lock().await.running
147 }
148
149 pub async fn wait(&self, timeout: Option<Duration>) -> Result<Option<WsMessage>> {
151 let buf = self.core.ws.lock().await.buf.clone();
152 let deadline = Instant::now() + timeout.unwrap_or_else(|| self.core.timeout());
153 loop {
154 if let Some(m) = buf.lock().await.pop_front() {
155 return Ok(Some(m));
156 }
157 if Instant::now() >= deadline {
158 return Ok(None);
159 }
160 sleep(Duration::from_millis(50)).await;
161 }
162 }
163
164 pub async fn wait_count(&self, n: usize, timeout: Option<Duration>) -> Result<Vec<WsMessage>> {
166 let buf = self.core.ws.lock().await.buf.clone();
167 let deadline = Instant::now() + timeout.unwrap_or_else(|| self.core.timeout());
168 let mut out = Vec::with_capacity(n);
169 while out.len() < n {
170 if let Some(m) = buf.lock().await.pop_front() {
171 out.push(m);
172 continue;
173 }
174 if Instant::now() >= deadline {
175 break;
176 }
177 sleep(Duration::from_millis(50)).await;
178 }
179 Ok(out)
180 }
181
182 pub async fn messages(&self) -> Vec<WsMessage> {
184 let buf = self.core.ws.lock().await.buf.clone();
185 let mut g = buf.lock().await;
186 g.drain(..).collect()
187 }
188
189 pub async fn stop(&self) -> Result<()> {
191 let (abort, buf) = {
192 let mut g = self.core.ws.lock().await;
193 g.running = false;
194 (g.abort.take(), g.buf.clone())
195 };
196 buf.lock().await.clear();
197 if let Some(a) = abort {
198 a.abort();
199 }
200 Ok(())
201 }
202}
203
204async fn ws_pump(
205 conn: Connection,
206 session_id: String,
207 filter: WsFilter,
208 buf: Arc<Mutex<VecDeque<WsMessage>>>,
209) {
210 let mut events = conn.subscribe();
211 let mut urls: HashMap<String, String> = HashMap::new();
212 loop {
213 let ev = match events.recv().await {
214 Ok(ev) => ev,
215 Err(RecvError::Lagged(_)) => continue,
216 Err(RecvError::Closed) => break,
217 };
218 if ev.session_id.as_deref() != Some(session_id.as_str()) {
219 continue;
220 }
221 match ev.method.as_str() {
222 "Network.webSocketCreated" => {
223 if let (Some(id), Some(url)) =
224 (ev.params["requestId"].as_str(), ev.params["url"].as_str())
225 {
226 urls.insert(id.to_string(), url.to_string());
227 }
228 }
229 "Network.webSocketFrameSent" | "Network.webSocketFrameReceived" => {
230 let dir = if ev.method.ends_with("Sent") {
231 WsDirection::Sent
232 } else {
233 WsDirection::Received
234 };
235 let id = ev.params["requestId"].as_str().unwrap_or_default();
236 let url = urls.get(id).cloned().unwrap_or_default();
237 let resp = &ev.params["response"];
238 let m = WsMessage {
239 direction: dir,
240 url,
241 opcode: resp["opcode"].as_u64().unwrap_or(0) as u8,
242 data: resp["payloadData"].as_str().unwrap_or_default().to_string(),
243 };
244 if filter.matches(&m) {
245 let mut g = buf.lock().await;
246 if g.len() >= BUFFER_CAP {
247 g.pop_front();
248 }
249 g.push_back(m);
250 }
251 }
252 _ => {}
253 }
254 }
255}
256
257pub(crate) type WsShared = EventBuf<WsMessage>;