use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use chromiumoxide_cdp::cdp::browser_protocol::accessibility::{
GetFullAxTreeParamsBuilder, GetFullAxTreeReturns, GetPartialAxTreeParamsBuilder,
GetPartialAxTreeReturns,
};
use tokio::sync::mpsc::{channel, Receiver, Sender};
use tokio::sync::oneshot::channel as oneshot_channel;
use chromiumoxide_cdp::cdp::browser_protocol::browser::{GetVersionParams, GetVersionReturns};
use chromiumoxide_cdp::cdp::browser_protocol::dom::{
BackendNodeId, DiscardSearchResultsParams, GetOuterHtmlParams, GetSearchResultsParams, NodeId,
PerformSearchParams, QuerySelectorAllParams, QuerySelectorParams, Rgba,
};
use chromiumoxide_cdp::cdp::browser_protocol::emulation::{
ClearDeviceMetricsOverrideParams, SetDefaultBackgroundColorOverrideParams,
SetDeviceMetricsOverrideParams,
};
use chromiumoxide_cdp::cdp::browser_protocol::input::{
DispatchDragEventParams, DispatchDragEventType, DispatchKeyEventParams, DispatchKeyEventType,
DispatchMouseEventParams, DispatchMouseEventType, DragData, MouseButton,
};
use chromiumoxide_cdp::cdp::browser_protocol::page::{
FrameId, GetLayoutMetricsParams, GetLayoutMetricsReturns, PrintToPdfParams, SetBypassCspParams,
Viewport,
};
use chromiumoxide_cdp::cdp::browser_protocol::target::{ActivateTargetParams, SessionId, TargetId};
use chromiumoxide_cdp::cdp::js_protocol::runtime::{
CallFunctionOnParams, CallFunctionOnReturns, EvaluateParams, ExecutionContextId, RemoteObjectId,
};
use chromiumoxide_types::{Command, CommandResponse};
use crate::cmd::{to_command_response, CommandMessage};
use crate::error::{CdpError, Result};
use crate::handler::commandfuture::CommandFuture;
use crate::handler::domworld::DOMWorldKind;
use crate::handler::httpfuture::HttpFuture;
use crate::handler::target::{GetExecutionContext, TargetMessage};
use crate::handler::target_message_future::TargetMessageFuture;
use crate::js::EvaluationResult;
use crate::layout::{Delta, Point, ScrollBehavior};
use crate::mouse::SmartMouse;
use crate::page::ScreenshotParams;
use crate::{keys, utils, ArcHttpRequest};
static ACTIVE_PAGES: AtomicUsize = AtomicUsize::new(0);
#[inline]
pub fn active_page_count() -> usize {
ACTIVE_PAGES.load(Ordering::Relaxed)
}
#[derive(Debug)]
pub struct PageHandle {
pub(crate) rx: Receiver<TargetMessage>,
page: Arc<PageInner>,
}
impl PageHandle {
pub fn new(
target_id: TargetId,
session_id: SessionId,
opener_id: Option<TargetId>,
request_timeout: std::time::Duration,
) -> Self {
let (commands, rx) = channel(100);
let page = PageInner {
target_id,
session_id,
opener_id,
sender: commands,
smart_mouse: SmartMouse::new(),
request_timeout,
};
ACTIVE_PAGES.fetch_add(1, Ordering::Relaxed);
Self {
rx,
page: Arc::new(page),
}
}
pub(crate) fn inner(&self) -> &Arc<PageInner> {
&self.page
}
}
#[derive(Debug)]
pub(crate) struct PageInner {
target_id: TargetId,
session_id: SessionId,
opener_id: Option<TargetId>,
sender: Sender<TargetMessage>,
pub(crate) smart_mouse: SmartMouse,
request_timeout: std::time::Duration,
}
impl Drop for PageInner {
fn drop(&mut self) {
ACTIVE_PAGES.fetch_sub(1, Ordering::Relaxed);
}
}
impl PageInner {
pub(crate) async fn execute<T: Command>(&self, cmd: T) -> Result<CommandResponse<T::Response>> {
execute(cmd, self.sender.clone(), Some(self.session_id.clone()), self.request_timeout).await
}
pub(crate) async fn send_command<T: Command>(&self, cmd: T) -> Result<&Self> {
let _ = send_command(cmd, self.sender.clone(), Some(self.session_id.clone()), self.request_timeout).await;
Ok(self)
}
pub(crate) fn command_future<T: Command>(&self, cmd: T) -> Result<CommandFuture<T>> {
CommandFuture::new(
cmd,
self.sender.clone(),
Some(self.session_id.clone()),
self.request_timeout,
)
}
pub(crate) fn wait_for_navigation(&self) -> TargetMessageFuture<ArcHttpRequest> {
TargetMessageFuture::<ArcHttpRequest>::wait_for_navigation(self.sender.clone(), self.request_timeout)
}
pub(crate) fn wait_for_network_idle(&self) -> TargetMessageFuture<ArcHttpRequest> {
TargetMessageFuture::<ArcHttpRequest>::wait_for_network_idle(self.sender.clone(), self.request_timeout)
}
pub(crate) fn wait_for_network_almost_idle(&self) -> TargetMessageFuture<ArcHttpRequest> {
TargetMessageFuture::<ArcHttpRequest>::wait_for_network_almost_idle(self.sender.clone(), self.request_timeout)
}
pub(crate) fn http_future<T: Command>(&self, cmd: T) -> Result<HttpFuture<T>> {
Ok(HttpFuture::new(
self.sender.clone(),
self.command_future(cmd)?,
self.request_timeout,
))
}
pub fn target_id(&self) -> &TargetId {
&self.target_id
}
pub fn session_id(&self) -> &SessionId {
&self.session_id
}
pub fn opener_id(&self) -> &Option<TargetId> {
&self.opener_id
}
pub(crate) async fn send_msg(&self, msg: TargetMessage) -> Result<()> {
tokio::time::timeout(self.request_timeout, self.sender.send(msg))
.await
.map_err(|_| CdpError::Timeout)?
.map_err(|_| CdpError::ChannelSendError(crate::error::ChannelError::Send))?;
Ok(())
}
pub async fn find_element(&self, selector: impl Into<String>, node: NodeId) -> Result<NodeId> {
Ok(self
.execute(QuerySelectorParams::new(node, selector))
.await?
.node_id)
}
pub async fn outer_html(
&self,
object_id: RemoteObjectId,
node_id: NodeId,
backend_node_id: BackendNodeId,
) -> Result<String> {
let cmd = GetOuterHtmlParams {
backend_node_id: Some(backend_node_id),
node_id: Some(node_id),
object_id: Some(object_id),
..Default::default()
};
let chromiumoxide_types::CommandResponse { result, .. } = self.execute(cmd).await?;
Ok(result.outer_html)
}
pub async fn activate(&self) -> Result<&Self> {
self.execute(ActivateTargetParams::new(self.target_id().clone()))
.await?;
Ok(self)
}
pub async fn version(&self) -> Result<GetVersionReturns> {
Ok(self.execute(GetVersionParams::default()).await?.result)
}
pub(crate) async fn find_elements(
&self,
selector: impl Into<String>,
node: NodeId,
) -> Result<Vec<NodeId>> {
Ok(self
.execute(QuerySelectorAllParams::new(node, selector))
.await?
.result
.node_ids)
}
pub async fn find_xpaths(&self, query: impl Into<String>) -> Result<Vec<NodeId>> {
let perform_search_returns = self
.execute(PerformSearchParams {
query: query.into(),
include_user_agent_shadow_dom: Some(true),
})
.await?
.result;
let search_results = self
.execute(GetSearchResultsParams::new(
perform_search_returns.search_id.clone(),
0,
perform_search_returns.result_count,
))
.await?
.result;
self.execute(DiscardSearchResultsParams::new(
perform_search_returns.search_id,
))
.await?;
Ok(search_results.node_ids)
}
pub async fn move_mouse(&self, point: Point) -> Result<&Self> {
self.smart_mouse.set_position(point);
self.execute(DispatchMouseEventParams::new(
DispatchMouseEventType::MouseMoved,
point.x,
point.y,
))
.await?;
Ok(self)
}
pub async fn move_mouse_smooth(&self, target: Point) -> Result<&Self> {
let path = self.smart_mouse.path_to(target);
for step in &path {
self.execute(DispatchMouseEventParams::new(
DispatchMouseEventType::MouseMoved,
step.point.x,
step.point.y,
))
.await?;
tokio::time::sleep(step.delay).await;
}
Ok(self)
}
pub fn mouse_position(&self) -> Point {
self.smart_mouse.position()
}
pub async fn scroll_by(
&self,
delta_x: f64,
delta_y: f64,
behavior: ScrollBehavior,
) -> Result<&Self> {
let behavior_str = match behavior {
ScrollBehavior::Auto => "auto",
ScrollBehavior::Instant => "instant",
ScrollBehavior::Smooth => "smooth",
};
self.evaluate_expression(format!(
"window.scrollBy({{top: {}, left: {}, behavior: '{}'}});",
delta_y, delta_x, behavior_str
))
.await?;
Ok(self)
}
pub async fn drag(
&self,
drag_type: DispatchDragEventType,
point: Point,
drag_data: DragData,
modifiers: Option<i64>,
) -> Result<&Self> {
let mut params: DispatchDragEventParams =
DispatchDragEventParams::new(drag_type, point.x, point.y, drag_data);
if let Some(modifiers) = modifiers {
params.modifiers = Some(modifiers);
}
self.execute(params).await?;
Ok(self)
}
pub async fn scroll(&self, point: Point, delta: Delta) -> Result<&Self> {
let mut params: DispatchMouseEventParams =
DispatchMouseEventParams::new(DispatchMouseEventType::MouseWheel, point.x, point.y);
params.delta_x = Some(delta.delta_x);
params.delta_y = Some(delta.delta_y);
self.execute(params).await?;
Ok(self)
}
pub async fn click_with_count_base(
&self,
point: Point,
click_count: impl Into<i64>,
modifiers: impl Into<i64>,
button: impl Into<MouseButton>,
) -> Result<&Self> {
let cmd = DispatchMouseEventParams::builder()
.x(point.x)
.y(point.y)
.button(button)
.click_count(click_count)
.modifiers(modifiers);
if let Ok(cmd) = cmd
.clone()
.r#type(DispatchMouseEventType::MousePressed)
.build()
{
self.move_mouse(point).await?.send_command(cmd).await?;
}
if let Ok(cmd) = cmd.r#type(DispatchMouseEventType::MouseReleased).build() {
self.execute(cmd).await?;
}
self.smart_mouse.set_position(point);
Ok(self)
}
pub async fn click_smooth(&self, point: Point) -> Result<&Self> {
self.move_mouse_smooth(point).await?;
self.click(point).await
}
pub async fn click_with_count(
&self,
point: Point,
click_count: impl Into<i64>,
modifiers: impl Into<i64>,
) -> Result<&Self> {
self.click_with_count_base(point, click_count, modifiers, MouseButton::Left)
.await
}
pub async fn right_click_with_count(
&self,
point: Point,
click_count: impl Into<i64>,
modifiers: impl Into<i64>,
) -> Result<&Self> {
self.click_with_count_base(point, click_count, modifiers, MouseButton::Right)
.await
}
pub async fn middle_click_with_count(
&self,
point: Point,
click_count: impl Into<i64>,
modifiers: impl Into<i64>,
) -> Result<&Self> {
self.click_with_count_base(point, click_count, modifiers, MouseButton::Middle)
.await
}
pub async fn back_click_with_count(
&self,
point: Point,
click_count: impl Into<i64>,
modifiers: impl Into<i64>,
) -> Result<&Self> {
self.click_with_count_base(point, click_count, modifiers, MouseButton::Back)
.await
}
pub async fn forward_click_with_count(
&self,
point: Point,
click_count: impl Into<i64>,
modifiers: impl Into<i64>,
) -> Result<&Self> {
self.click_with_count_base(point, click_count, modifiers, MouseButton::Forward)
.await
}
pub async fn click_and_drag(
&self,
from: Point,
to: Point,
modifiers: impl Into<i64>,
) -> Result<&Self> {
let modifiers = modifiers.into();
let click_count = 1;
let cmd = DispatchMouseEventParams::builder()
.button(MouseButton::Left)
.click_count(click_count)
.modifiers(modifiers);
if let Ok(cmd) = cmd
.clone()
.x(from.x)
.y(from.y)
.r#type(DispatchMouseEventType::MousePressed)
.build()
{
self.move_mouse(from).await?.send_command(cmd).await?;
}
if let Ok(cmd) = cmd
.clone()
.x(to.x)
.y(to.y)
.r#type(DispatchMouseEventType::MouseMoved)
.build()
{
self.move_mouse(to).await?.send_command(cmd).await?;
}
if let Ok(cmd) = cmd
.r#type(DispatchMouseEventType::MouseReleased)
.x(to.x)
.y(to.y)
.build()
{
self.send_command(cmd).await?;
}
self.smart_mouse.set_position(to);
Ok(self)
}
pub async fn click_and_drag_smooth(
&self,
from: Point,
to: Point,
modifiers: impl Into<i64>,
) -> Result<&Self> {
let modifiers = modifiers.into();
self.move_mouse_smooth(from).await?;
if let Ok(cmd) = DispatchMouseEventParams::builder()
.x(from.x)
.y(from.y)
.button(MouseButton::Left)
.click_count(1)
.modifiers(modifiers)
.r#type(DispatchMouseEventType::MousePressed)
.build()
{
self.send_command(cmd).await?;
}
let path = self.smart_mouse.path_to(to);
for step in &path {
if let Ok(cmd) = DispatchMouseEventParams::builder()
.x(step.point.x)
.y(step.point.y)
.button(MouseButton::Left)
.modifiers(modifiers)
.r#type(DispatchMouseEventType::MouseMoved)
.build()
{
self.send_command(cmd).await?;
}
tokio::time::sleep(step.delay).await;
}
if let Ok(cmd) = DispatchMouseEventParams::builder()
.x(to.x)
.y(to.y)
.button(MouseButton::Left)
.click_count(1)
.modifiers(modifiers)
.r#type(DispatchMouseEventType::MouseReleased)
.build()
{
self.send_command(cmd).await?;
}
Ok(self)
}
pub async fn click(&self, point: Point) -> Result<&Self> {
self.click_with_count(point, 1, 0).await
}
pub async fn double_click(&self, point: Point) -> Result<&Self> {
self.click_with_count(point, 2, 0).await
}
pub async fn right_click(&self, point: Point) -> Result<&Self> {
self.right_click_with_count(point, 1, 0).await
}
pub async fn middle_click(&self, point: Point) -> Result<&Self> {
self.middle_click_with_count(point, 1, 0).await
}
pub async fn back_click(&self, point: Point) -> Result<&Self> {
self.back_click_with_count(point, 1, 0).await
}
pub async fn forward_click(&self, point: Point) -> Result<&Self> {
self.forward_click_with_count(point, 1, 0).await
}
pub async fn click_with_modifier(
&self,
point: Point,
modifiers: impl Into<i64>,
) -> Result<&Self> {
self.click_with_count(point, 1, modifiers).await
}
pub async fn right_click_with_modifier(
&self,
point: Point,
modifiers: impl Into<i64>,
) -> Result<&Self> {
self.right_click_with_count(point, 1, modifiers).await
}
pub async fn middle_click_with_modifier(
&self,
point: Point,
modifiers: impl Into<i64>,
) -> Result<&Self> {
self.middle_click_with_count(point, 1, modifiers).await
}
pub async fn double_click_with_modifier(
&self,
point: Point,
modifiers: impl Into<i64>,
) -> Result<&Self> {
self.click_with_count(point, 2, modifiers).await
}
pub async fn type_str(&self, input: impl AsRef<str>) -> Result<&Self> {
for c in input.as_ref().split("").filter(|s| !s.is_empty()) {
self._press_key(c, None).await?;
}
Ok(self)
}
pub async fn get_full_ax_tree(
&self,
depth: Option<i64>,
frame_id: Option<FrameId>,
) -> Result<GetFullAxTreeReturns> {
let mut builder = GetFullAxTreeParamsBuilder::default();
if let Some(depth) = depth {
builder = builder.depth(depth);
}
if let Some(frame_id) = frame_id {
builder = builder.frame_id(frame_id);
}
let resp = self.execute(builder.build()).await?;
Ok(resp.result)
}
pub async fn get_partial_ax_tree(
&self,
node_id: Option<chromiumoxide_cdp::cdp::browser_protocol::dom::NodeId>,
backend_node_id: Option<BackendNodeId>,
object_id: Option<RemoteObjectId>,
fetch_relatives: Option<bool>,
) -> Result<GetPartialAxTreeReturns> {
let mut builder = GetPartialAxTreeParamsBuilder::default();
if let Some(node_id) = node_id {
builder = builder.node_id(node_id);
}
if let Some(backend_node_id) = backend_node_id {
builder = builder.backend_node_id(backend_node_id);
}
if let Some(object_id) = object_id {
builder = builder.object_id(object_id);
}
if let Some(fetch_relatives) = fetch_relatives {
builder = builder.fetch_relatives(fetch_relatives);
}
let resp = self.execute(builder.build()).await?;
Ok(resp.result)
}
pub async fn type_str_with_modifier(
&self,
input: impl AsRef<str>,
modifiers: Option<i64>,
) -> Result<&Self> {
for c in input.as_ref().split("").filter(|s| !s.is_empty()) {
self._press_key(c, modifiers).await?;
}
Ok(self)
}
async fn _press_key(&self, key: impl AsRef<str>, modifiers: Option<i64>) -> Result<&Self> {
let key = key.as_ref();
let key_definition = keys::get_key_definition(key)
.ok_or_else(|| CdpError::msg(format!("Key not found: {key}")))?;
let mut cmd = DispatchKeyEventParams::builder();
let key_down_event_type = if let Some(txt) = key_definition.text {
cmd = cmd.text(txt);
DispatchKeyEventType::KeyDown
} else if key_definition.key.len() == 1 {
cmd = cmd.text(key_definition.key);
DispatchKeyEventType::KeyDown
} else {
DispatchKeyEventType::RawKeyDown
};
cmd = cmd
.r#type(DispatchKeyEventType::KeyDown)
.key(key_definition.key)
.code(key_definition.code)
.windows_virtual_key_code(key_definition.key_code)
.native_virtual_key_code(key_definition.key_code);
if let Some(modifiers) = modifiers {
cmd = cmd.modifiers(modifiers);
}
if let Ok(cmd) = cmd.clone().r#type(key_down_event_type).build() {
self.execute(cmd).await?;
}
if let Ok(cmd) = cmd.r#type(DispatchKeyEventType::KeyUp).build() {
self.execute(cmd).await?;
}
Ok(self)
}
pub async fn press_key(&self, key: impl AsRef<str>) -> Result<&Self> {
self._press_key(key, None).await
}
pub async fn press_key_with_modifier(
&self,
key: impl AsRef<str>,
modifiers: Option<i64>,
) -> Result<&Self> {
self._press_key(key, modifiers).await
}
pub async fn call_js_fn(
&self,
function_declaration: impl Into<String>,
await_promise: bool,
remote_object_id: RemoteObjectId,
) -> Result<CallFunctionOnReturns> {
if let Ok(resp) = CallFunctionOnParams::builder()
.object_id(remote_object_id)
.function_declaration(function_declaration)
.generate_preview(true)
.await_promise(await_promise)
.build()
{
let resp = self.execute(resp).await?;
Ok(resp.result)
} else {
Err(CdpError::NotFound)
}
}
pub async fn evaluate_expression(
&self,
evaluate: impl Into<EvaluateParams>,
) -> Result<EvaluationResult> {
let mut evaluate = evaluate.into();
if evaluate.context_id.is_none() {
evaluate.context_id = self.execution_context().await?;
}
if evaluate.await_promise.is_none() {
evaluate.await_promise = Some(true);
}
if evaluate.return_by_value.is_none() {
evaluate.return_by_value = Some(true);
}
let resp = self.execute(evaluate).await?.result;
if let Some(exception) = resp.exception_details {
return Err(CdpError::JavascriptException(Box::new(exception)));
}
Ok(EvaluationResult::new(resp.result))
}
pub async fn evaluate_function(
&self,
evaluate: impl Into<CallFunctionOnParams>,
) -> Result<EvaluationResult> {
let mut evaluate = evaluate.into();
if evaluate.execution_context_id.is_none() {
evaluate.execution_context_id = self.execution_context().await?;
}
if evaluate.await_promise.is_none() {
evaluate.await_promise = Some(true);
}
if evaluate.return_by_value.is_none() {
evaluate.return_by_value = Some(true);
}
let resp = self.execute(evaluate).await?.result;
if let Some(exception) = resp.exception_details {
return Err(CdpError::JavascriptException(Box::new(exception)));
}
Ok(EvaluationResult::new(resp.result))
}
pub async fn execution_context(&self) -> Result<Option<ExecutionContextId>> {
self.execution_context_for_world(None, DOMWorldKind::Main)
.await
}
pub async fn secondary_execution_context(&self) -> Result<Option<ExecutionContextId>> {
self.execution_context_for_world(None, DOMWorldKind::Secondary)
.await
}
pub async fn frame_execution_context(
&self,
frame_id: FrameId,
) -> Result<Option<ExecutionContextId>> {
self.execution_context_for_world(Some(frame_id), DOMWorldKind::Main)
.await
}
pub async fn frame_secondary_execution_context(
&self,
frame_id: FrameId,
) -> Result<Option<ExecutionContextId>> {
self.execution_context_for_world(Some(frame_id), DOMWorldKind::Secondary)
.await
}
pub async fn execution_context_for_world(
&self,
frame_id: Option<FrameId>,
dom_world: DOMWorldKind,
) -> Result<Option<ExecutionContextId>> {
let (tx, rx) = oneshot_channel();
tokio::time::timeout(
self.request_timeout,
self.sender
.send(TargetMessage::GetExecutionContext(GetExecutionContext {
dom_world,
frame_id,
tx,
})),
)
.await
.map_err(|_| CdpError::Timeout)?
.map_err(|_| CdpError::ChannelSendError(crate::error::ChannelError::Send))?;
Ok(tokio::time::timeout(self.request_timeout, rx)
.await
.map_err(|_| CdpError::Timeout)??)
}
pub async fn layout_metrics(&self) -> Result<GetLayoutMetricsReturns> {
Ok(self
.execute(GetLayoutMetricsParams::default())
.await?
.result)
}
pub async fn set_bypass_csp(&self, enabled: bool) -> Result<&Self> {
self.execute(SetBypassCspParams::new(enabled)).await?;
Ok(self)
}
pub async fn screenshot(&self, params: impl Into<ScreenshotParams>) -> Result<Vec<u8>> {
self.activate().await?;
let params = params.into();
let full_page = params.full_page();
let omit_background = params.omit_background();
let mut cdp_params = params.cdp_params;
if full_page {
let metrics = self.layout_metrics().await?;
let width = metrics.css_content_size.width;
let height = metrics.css_content_size.height;
cdp_params.clip = Some(Viewport {
x: 0.,
y: 0.,
width,
height,
scale: 1.,
});
self.execute(SetDeviceMetricsOverrideParams::new(
width as i64,
height as i64,
1.,
false,
))
.await?;
}
if omit_background {
self.execute(SetDefaultBackgroundColorOverrideParams {
color: Some(Rgba {
r: 0,
g: 0,
b: 0,
a: Some(0.),
}),
})
.await?;
}
let res = self.execute(cdp_params).await?.result;
if omit_background {
self.send_command(SetDefaultBackgroundColorOverrideParams { color: None })
.await?;
}
if full_page {
self.send_command(ClearDeviceMetricsOverrideParams {})
.await?;
}
Ok(utils::base64::decode(&res.data)?)
}
pub async fn print_to_pdf(&self, params: impl Into<PrintToPdfParams>) -> Result<Vec<u8>> {
self.activate().await?;
let params = params.into();
let res = self.execute(params).await?.result;
Ok(utils::base64::decode(&res.data)?)
}
}
pub(crate) async fn execute<T: Command>(
cmd: T,
sender: Sender<TargetMessage>,
session: Option<SessionId>,
request_timeout: std::time::Duration,
) -> Result<CommandResponse<T::Response>> {
let method = cmd.identifier();
let rx = send_command(cmd, sender, session, request_timeout).await?;
let resp = tokio::time::timeout(request_timeout, rx)
.await
.map_err(|_| CdpError::Timeout)???;
to_command_response::<T>(resp, method)
}
pub(crate) async fn send_command<T: Command>(
cmd: T,
sender: Sender<TargetMessage>,
session: Option<SessionId>,
request_timeout: std::time::Duration,
) -> Result<tokio::sync::oneshot::Receiver<Result<chromiumoxide_types::Response, CdpError>>> {
let (tx, rx) = oneshot_channel();
let msg = CommandMessage::with_session(cmd, tx, session)?;
tokio::time::timeout(request_timeout, sender.send(TargetMessage::Command(msg)))
.await
.map_err(|_| CdpError::Timeout)?
.map_err(|_| CdpError::ChannelSendError(crate::error::ChannelError::Send))?;
Ok(rx)
}