use crate::cdp::session::CdpSession;
use crate::cdp::CdpEvent;
use parking_lot::Mutex;
use serde_json::Value;
use std::collections::HashMap;
use std::future::Future;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{broadcast, Mutex as AsyncMutex};
#[derive(Debug, Clone)]
pub struct WebSocketFrame {
pub direction: FrameDirection,
pub data: FrameData,
pub opcode: Option<i64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrameDirection {
Sent,
Received,
}
#[derive(Debug, Clone)]
pub enum FrameData {
Text(String),
Binary(Vec<u8>),
}
#[derive(Debug, Clone, Default)]
pub struct WebSocketHandshake {
pub status: Option<i64>,
pub status_text: Option<String>,
pub response_headers: HashMap<String, String>,
pub request_headers: HashMap<String, String>,
}
#[derive(Debug, Clone, Default)]
pub struct WebSocketCloseInfo {
pub timestamp: Option<f64>,
}
#[derive(Clone)]
pub struct WebSocket {
inner: Arc<WebSocketInner>,
}
struct WebSocketInner {
request_id: String,
url: Mutex<String>,
handshake: Mutex<WebSocketHandshake>,
frames: Mutex<Vec<WebSocketFrame>>,
error: Mutex<Option<String>>,
close: Mutex<Option<WebSocketCloseInfo>>,
events: broadcast::Sender<WebSocketLiveEvent>,
}
#[derive(Debug, Clone)]
pub enum WebSocketLiveEvent {
Frame(WebSocketFrame),
Error(String),
Closed(WebSocketCloseInfo),
}
impl WebSocket {
pub(crate) fn new(request_id: impl Into<String>, url: impl Into<String>) -> Self {
let (tx, _rx) = broadcast::channel(256);
Self {
inner: Arc::new(WebSocketInner {
request_id: request_id.into(),
url: Mutex::new(url.into()),
handshake: Mutex::new(WebSocketHandshake::default()),
frames: Mutex::new(Vec::new()),
error: Mutex::new(None),
close: Mutex::new(None),
events: tx,
}),
}
}
pub fn request_id(&self) -> &str {
&self.inner.request_id
}
pub fn url(&self) -> String {
self.inner.url.lock().clone()
}
pub fn handshake(&self) -> WebSocketHandshake {
self.inner.handshake.lock().clone()
}
pub fn frames(&self) -> Vec<WebSocketFrame> {
self.inner.frames.lock().clone()
}
pub fn error(&self) -> Option<String> {
self.inner.error.lock().clone()
}
pub fn close_info(&self) -> Option<WebSocketCloseInfo> {
self.inner.close.lock().clone()
}
pub fn on_frame<F, Fut>(&self, handler: F)
where
F: Fn(WebSocketFrame) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
let mut rx = self.inner.events.subscribe();
tokio::spawn(async move {
while let Ok(ev) = rx.recv().await {
if let WebSocketLiveEvent::Frame(f) = ev {
handler(f).await;
}
}
});
}
pub fn on_frame_received<F, Fut>(&self, handler: F)
where
F: Fn(WebSocketFrame) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
let mut rx = self.inner.events.subscribe();
tokio::spawn(async move {
while let Ok(ev) = rx.recv().await {
if let WebSocketLiveEvent::Frame(f) = ev {
if f.direction == FrameDirection::Received {
handler(f).await;
}
}
}
});
}
pub fn on_frame_sent<F, Fut>(&self, handler: F)
where
F: Fn(WebSocketFrame) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
let mut rx = self.inner.events.subscribe();
tokio::spawn(async move {
while let Ok(ev) = rx.recv().await {
if let WebSocketLiveEvent::Frame(f) = ev {
if f.direction == FrameDirection::Sent {
handler(f).await;
}
}
}
});
}
pub fn on_close<F, Fut>(&self, handler: F)
where
F: Fn(WebSocketCloseInfo) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
let mut rx = self.inner.events.subscribe();
tokio::spawn(async move {
while let Ok(ev) = rx.recv().await {
if let WebSocketLiveEvent::Closed(c) = ev {
handler(c).await;
}
}
});
}
pub async fn wait_for_event(&self, timeout: Duration) -> Result<WebSocketLiveEvent, WaitError> {
let mut rx = self.inner.events.subscribe();
match tokio::time::timeout(timeout, rx.recv()).await {
Ok(Ok(ev)) => Ok(ev),
Ok(Err(broadcast::error::RecvError::Closed)) => Err(WaitError::Closed),
Ok(Err(broadcast::error::RecvError::Lagged(_))) => Err(WaitError::Lagged),
Err(_) => Err(WaitError::Timeout),
}
}
fn ingest(&self, method: &str, params: &Value) {
match method {
"Network.webSocketCreated" => {
if let Some(url) = params.get("url").and_then(|v| v.as_str()) {
*self.inner.url.lock() = url.to_string();
}
}
"Network.webSocketWillSendHandshakeRequest" => {
if let Some(req) = params.get("request") {
let mut hs = self.inner.handshake.lock();
if let Some(headers) = req.get("headers").and_then(|v| v.as_object()) {
hs.request_headers = headers_to_map(headers);
}
}
}
"Network.webSocketHandshakeResponseReceived"
| "Network.webSocketHandshakeResponse" => {
if let Some(resp) = params.get("response") {
let mut hs = self.inner.handshake.lock();
hs.status = resp.get("status").and_then(|v| v.as_i64());
hs.status_text = resp
.get("statusText")
.and_then(|v| v.as_str())
.map(String::from);
if let Some(headers) = resp.get("headers").and_then(|v| v.as_object()) {
hs.response_headers = headers_to_map(headers);
}
}
}
"Network.webSocketFrameSent" => {
self.record_frame(FrameDirection::Sent, params);
}
"Network.webSocketFrameReceived" => {
self.record_frame(FrameDirection::Received, params);
}
"Network.webSocketFrameError" => {
let msg = params
.get("errorMessage")
.and_then(|v| v.as_str())
.unwrap_or("websocket frame error")
.to_string();
*self.inner.error.lock() = Some(msg.clone());
let _ = self.inner.events.send(WebSocketLiveEvent::Error(msg));
}
"Network.webSocketClosed" => {
let info = WebSocketCloseInfo {
timestamp: params.get("timestamp").and_then(|v| v.as_f64()),
};
*self.inner.close.lock() = Some(info.clone());
let _ = self.inner.events.send(WebSocketLiveEvent::Closed(info));
}
_ => {}
}
}
fn record_frame(&self, direction: FrameDirection, params: &Value) {
let frame = params
.get("response")
.and_then(|f| parse_frame(direction, f));
if let Some(frame) = frame {
self.inner.frames.lock().push(frame.clone());
let _ = self.inner.events.send(WebSocketLiveEvent::Frame(frame));
}
}
}
fn parse_frame(direction: FrameDirection, frame: &Value) -> Option<WebSocketFrame> {
let opcode = frame.get("opcode").and_then(|v| v.as_i64());
let payload = frame.get("payloadData").and_then(|v| v.as_str())?;
let data = if opcode == Some(2) {
FrameData::Binary(payload.as_bytes().to_vec())
} else {
FrameData::Text(payload.to_string())
};
Some(WebSocketFrame {
direction,
data,
opcode,
})
}
fn headers_to_map(headers: &serde_json::Map<String, Value>) -> HashMap<String, String> {
let mut out = HashMap::with_capacity(headers.len());
for (k, v) in headers {
let val = match v {
Value::String(s) => s.clone(),
other => other.to_string(),
};
out.insert(k.clone(), val);
}
out
}
#[derive(Debug)]
pub enum WaitError {
Timeout,
Closed,
Lagged,
}
#[derive(Clone)]
pub struct WebSocketRegistry {
inner: Arc<WebSocketRegistryInner>,
}
struct WebSocketRegistryInner {
sockets: AsyncMutex<HashMap<String, WebSocket>>,
created: broadcast::Sender<WebSocket>,
}
impl WebSocketRegistry {
pub(crate) fn new() -> Self {
let (tx, _rx) = broadcast::channel(64);
Self {
inner: Arc::new(WebSocketRegistryInner {
sockets: AsyncMutex::new(HashMap::new()),
created: tx,
}),
}
}
pub fn attach(&self, session: &Arc<CdpSession>) {
let mut rx = session.subscribe();
let inner = Arc::clone(&self.inner);
tokio::spawn(async move {
loop {
match rx.recv().await {
Ok(ev) => dispatch(&inner, &ev).await,
Err(broadcast::error::RecvError::Closed) => break,
Err(broadcast::error::RecvError::Lagged(_)) => continue,
}
}
});
}
pub async fn get(&self, request_id: &str) -> Option<WebSocket> {
self.inner.sockets.lock().await.get(request_id).cloned()
}
pub async fn all(&self) -> Vec<WebSocket> {
self.inner.sockets.lock().await.values().cloned().collect()
}
pub fn on_created(&self) -> broadcast::Receiver<WebSocket> {
self.inner.created.subscribe()
}
}
async fn dispatch(reg: &WebSocketRegistryInner, ev: &CdpEvent) {
let request_id = match ev.params.get("requestId").and_then(|v| v.as_str()) {
Some(id) => id.to_string(),
None => return,
};
if ev.method == "Network.webSocketCreated" {
let url = ev
.params
.get("url")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let ws = WebSocket::new(&request_id, &url);
let mut sockets = reg.sockets.lock().await;
sockets.entry(request_id.clone()).or_insert_with(|| ws.clone());
drop(sockets);
let _ = reg.created.send(ws);
return;
}
let ws = {
let mut sockets = reg.sockets.lock().await;
sockets
.entry(request_id.clone())
.or_insert_with(|| WebSocket::new(&request_id, ""))
.clone()
};
ws.ingest(&ev.method, &ev.params);
}