1use crate::listeners::{EventListenerRequest, EventListeners};
2use chromiumoxide_cdp::cdp::browser_protocol::browser::*;
3use chromiumoxide_cdp::cdp::browser_protocol::target::*;
4use chromiumoxide_cdp::cdp::events::CdpEvent;
5use chromiumoxide_cdp::cdp::events::CdpEventMessage;
6use chromiumoxide_types::{CallId, Message, Method, Response};
7use chromiumoxide_types::{MethodId, Request as CdpRequest};
8use fnv::FnvHashMap;
9use futures::channel::mpsc::Receiver;
10use futures::channel::oneshot::Sender as OneshotSender;
11use futures::stream::{Fuse, Stream, StreamExt};
12use futures::task::{Context, Poll};
13use hashbrown::{HashMap, HashSet};
14pub(crate) use page::PageInner;
15use spider_network_blocker::intercept_manager::NetworkInterceptManager;
16use std::pin::Pin;
17use std::time::{Duration, Instant};
18use tokio_tungstenite::tungstenite::error::ProtocolError;
19use tokio_tungstenite::tungstenite::Error;
20
21use crate::cmd::{to_command_response, CommandMessage};
22use crate::conn::Connection;
23use crate::error::{CdpError, Result};
24use crate::handler::browser::BrowserContext;
25use crate::handler::frame::FrameRequestedNavigation;
26use crate::handler::frame::{NavigationError, NavigationId, NavigationOk};
27use crate::handler::job::PeriodicJob;
28use crate::handler::session::Session;
29use crate::handler::target::TargetEvent;
30use crate::handler::target::{Target, TargetConfig};
31use crate::handler::viewport::Viewport;
32use crate::page::Page;
33
34pub const REQUEST_TIMEOUT: u64 = 30_000;
36
37pub mod blockers;
38pub mod browser;
39pub mod commandfuture;
40pub mod domworld;
41pub mod emulation;
42pub mod frame;
43pub mod http;
44pub mod httpfuture;
45mod job;
46pub mod network;
47mod page;
48mod session;
49pub mod target;
50pub mod target_message_future;
51pub mod viewport;
52
53#[must_use = "streams do nothing unless polled"]
56#[derive(Debug)]
57pub struct Handler {
58 pub default_browser_context: BrowserContext,
59 pub browser_contexts: HashSet<BrowserContext>,
60 pending_commands: FnvHashMap<CallId, (PendingRequest, MethodId, Instant)>,
64 from_browser: Fuse<Receiver<HandlerMessage>>,
66 target_ids: Vec<TargetId>,
68 targets: HashMap<TargetId, Target>,
70 navigations: FnvHashMap<NavigationId, NavigationRequest>,
72 sessions: HashMap<SessionId, Session>,
76 conn: Connection<CdpEventMessage>,
78 evict_command_timeout: PeriodicJob,
80 next_navigation_id: usize,
82 config: HandlerConfig,
84 event_listeners: EventListeners,
86 closing: bool,
88}
89
90lazy_static::lazy_static! {
91 static ref DISCOVER_ID: (std::borrow::Cow<'static, str>, serde_json::Value) = {
93 let discover = SetDiscoverTargetsParams::new(true);
94 (discover.identifier(), serde_json::to_value(discover).expect("valid discover target params"))
95 };
96 static ref TARGET_PARAMS_ID: (std::borrow::Cow<'static, str>, serde_json::Value) = {
98 let msg = GetTargetsParams { filter: None };
99 (msg.identifier(), serde_json::to_value(msg).expect("valid paramtarget"))
100 };
101 static ref CLOSE_PARAMS_ID: (std::borrow::Cow<'static, str>, serde_json::Value) = {
103 let close_msg = CloseParams::default();
104 (close_msg.identifier(), serde_json::to_value(close_msg).expect("valid close params"))
105 };
106}
107
108impl Handler {
109 pub(crate) fn new(
112 mut conn: Connection<CdpEventMessage>,
113 rx: Receiver<HandlerMessage>,
114 config: HandlerConfig,
115 ) -> Self {
116 let discover = DISCOVER_ID.clone();
117 let _ = conn.submit_command(discover.0, None, discover.1);
118
119 let browser_contexts = config
120 .context_ids
121 .iter()
122 .map(|id| BrowserContext::from(id.clone()))
123 .collect();
124
125 Self {
126 pending_commands: Default::default(),
127 from_browser: rx.fuse(),
128 default_browser_context: Default::default(),
129 browser_contexts,
130 target_ids: Default::default(),
131 targets: Default::default(),
132 navigations: Default::default(),
133 sessions: Default::default(),
134 conn,
135 evict_command_timeout: PeriodicJob::new(config.request_timeout),
136 next_navigation_id: 0,
137 config,
138 event_listeners: Default::default(),
139 closing: false,
140 }
141 }
142
143 pub fn get_target(&self, target_id: &TargetId) -> Option<&Target> {
145 self.targets.get(target_id)
146 }
147
148 pub fn targets(&self) -> impl Iterator<Item = &Target> + '_ {
150 self.targets.values()
151 }
152
153 pub fn default_browser_context(&self) -> &BrowserContext {
155 &self.default_browser_context
156 }
157
158 pub fn browser_contexts(&self) -> impl Iterator<Item = &BrowserContext> + '_ {
160 self.browser_contexts.iter()
161 }
162
163 fn on_navigation_response(&mut self, id: NavigationId, resp: Response) {
165 if let Some(nav) = self.navigations.remove(&id) {
166 match nav {
167 NavigationRequest::Navigate(mut nav) => {
168 if nav.navigated {
169 let _ = nav.tx.send(Ok(resp));
170 } else {
171 nav.set_response(resp);
172 self.navigations
173 .insert(id, NavigationRequest::Navigate(nav));
174 }
175 }
176 }
177 }
178 }
179
180 fn on_navigation_lifecycle_completed(&mut self, res: Result<NavigationOk, NavigationError>) {
182 match res {
183 Ok(ok) => {
184 let id = *ok.navigation_id();
185 if let Some(nav) = self.navigations.remove(&id) {
186 match nav {
187 NavigationRequest::Navigate(mut nav) => {
188 if let Some(resp) = nav.response.take() {
189 let _ = nav.tx.send(Ok(resp));
190 } else {
191 nav.set_navigated();
192 self.navigations
193 .insert(id, NavigationRequest::Navigate(nav));
194 }
195 }
196 }
197 }
198 }
199 Err(err) => {
200 if let Some(nav) = self.navigations.remove(err.navigation_id()) {
201 match nav {
202 NavigationRequest::Navigate(nav) => {
203 let _ = nav.tx.send(Err(err.into()));
204 }
205 }
206 }
207 }
208 }
209 }
210
211 fn on_response(&mut self, resp: Response) {
213 if let Some((req, method, _)) = self.pending_commands.remove(&resp.id) {
214 match req {
215 PendingRequest::CreateTarget(tx) => {
216 match to_command_response::<CreateTargetParams>(resp, method) {
217 Ok(resp) => {
218 if let Some(target) = self.targets.get_mut(&resp.target_id) {
219 target.set_initiator(tx);
222 }
223 }
224 Err(err) => {
225 let _ = tx.send(Err(err)).ok();
226 }
227 }
228 }
229 PendingRequest::GetTargets(tx) => {
230 match to_command_response::<GetTargetsParams>(resp, method) {
231 Ok(resp) => {
232 let targets: Vec<TargetInfo> = resp.result.target_infos;
233 let results = targets.clone();
234 for target_info in targets {
235 let target_id = target_info.target_id.clone();
236 let event: EventTargetCreated = EventTargetCreated { target_info };
237 self.on_target_created(event);
238 let attach = AttachToTargetParams::new(target_id);
239 let method = attach.identifier();
240
241 if let Ok(params) = serde_json::to_value(attach) {
242 let _ = self.conn.submit_command(method, None, params);
243 }
244 }
245
246 let _ = tx.send(Ok(results)).ok();
247 }
248 Err(err) => {
249 let _ = tx.send(Err(err)).ok();
250 }
251 }
252 }
253 PendingRequest::Navigate(id) => {
254 self.on_navigation_response(id, resp);
255 if self.config.only_html && !self.config.created_first_target {
256 self.config.created_first_target = true;
257 }
258 }
259 PendingRequest::ExternalCommand(tx) => {
260 let _ = tx.send(Ok(resp)).ok();
261 }
262 PendingRequest::InternalCommand(target_id) => {
263 if let Some(target) = self.targets.get_mut(&target_id) {
264 target.on_response(resp, method.as_ref());
265 }
266 }
267 PendingRequest::CloseBrowser(tx) => {
268 self.closing = true;
269 let _ = tx.send(Ok(CloseReturns {})).ok();
270 }
271 }
272 }
273 }
274
275 pub(crate) fn submit_external_command(
277 &mut self,
278 msg: CommandMessage,
279 now: Instant,
280 ) -> Result<()> {
281 let call_id = self
282 .conn
283 .submit_command(msg.method.clone(), msg.session_id, msg.params)?;
284 self.pending_commands.insert(
285 call_id,
286 (PendingRequest::ExternalCommand(msg.sender), msg.method, now),
287 );
288 Ok(())
289 }
290
291 pub(crate) fn submit_internal_command(
292 &mut self,
293 target_id: TargetId,
294 req: CdpRequest,
295 now: Instant,
296 ) -> Result<()> {
297 let call_id = self.conn.submit_command(
298 req.method.clone(),
299 req.session_id.map(Into::into),
300 req.params,
301 )?;
302 self.pending_commands.insert(
303 call_id,
304 (PendingRequest::InternalCommand(target_id), req.method, now),
305 );
306 Ok(())
307 }
308
309 fn submit_fetch_targets(&mut self, tx: OneshotSender<Result<Vec<TargetInfo>>>, now: Instant) {
310 let msg = TARGET_PARAMS_ID.clone();
311
312 if let Ok(call_id) = self.conn.submit_command(msg.0.clone(), None, msg.1) {
313 self.pending_commands
314 .insert(call_id, (PendingRequest::GetTargets(tx), msg.0, now));
315 }
316 }
317
318 fn submit_navigation(&mut self, id: NavigationId, req: CdpRequest, now: Instant) {
321 if let Ok(call_id) = self.conn.submit_command(
322 req.method.clone(),
323 req.session_id.map(Into::into),
324 req.params,
325 ) {
326 self.pending_commands
327 .insert(call_id, (PendingRequest::Navigate(id), req.method, now));
328 }
329 }
330
331 fn submit_close(&mut self, tx: OneshotSender<Result<CloseReturns>>, now: Instant) {
332 let close_msg = CLOSE_PARAMS_ID.clone();
333
334 if let Ok(call_id) = self
335 .conn
336 .submit_command(close_msg.0.clone(), None, close_msg.1)
337 {
338 self.pending_commands.insert(
339 call_id,
340 (PendingRequest::CloseBrowser(tx), close_msg.0, now),
341 );
342 }
343 }
344
345 fn on_target_message(&mut self, target: &mut Target, msg: CommandMessage, now: Instant) {
347 if msg.is_navigation() {
348 let (req, tx) = msg.split();
349 let id = self.next_navigation_id();
350
351 target.goto(FrameRequestedNavigation::new(id, req));
352
353 self.navigations.insert(
354 id,
355 NavigationRequest::Navigate(NavigationInProgress::new(tx)),
356 );
357 } else {
358 let _ = self.submit_external_command(msg, now);
359 }
360 }
361
362 fn next_navigation_id(&mut self) -> NavigationId {
364 let id = NavigationId(self.next_navigation_id);
365 self.next_navigation_id = self.next_navigation_id.wrapping_add(1);
366 id
367 }
368
369 fn create_page(&mut self, params: CreateTargetParams, tx: OneshotSender<Result<Page>>) {
380 let about_blank = params.url == "about:blank";
381 let http_check =
382 !about_blank && params.url.starts_with("http") || params.url.starts_with("file://");
383
384 if about_blank || http_check {
385 let method = params.identifier();
386
387 match serde_json::to_value(params) {
388 Ok(params) => match self.conn.submit_command(method.clone(), None, params) {
389 Ok(call_id) => {
390 self.pending_commands.insert(
391 call_id,
392 (PendingRequest::CreateTarget(tx), method, Instant::now()),
393 );
394 }
395 Err(err) => {
396 let _ = tx.send(Err(err.into())).ok();
397 }
398 },
399 Err(err) => {
400 let _ = tx.send(Err(err.into())).ok();
401 }
402 }
403 } else {
404 let _ = tx.send(Err(CdpError::NotFound)).ok();
405 }
406 }
407
408 fn on_event(&mut self, event: CdpEventMessage) {
410 if let Some(ref session_id) = event.session_id {
411 if let Some(session) = self.sessions.get(session_id.as_str()) {
412 if let Some(target) = self.targets.get_mut(session.target_id()) {
413 return target.on_event(event);
414 }
415 }
416 }
417 let CdpEventMessage { params, method, .. } = event;
418
419 match params {
420 CdpEvent::TargetTargetCreated(ref ev) => self.on_target_created(ev.clone()),
421 CdpEvent::TargetAttachedToTarget(ref ev) => self.on_attached_to_target(ev.clone()),
422 CdpEvent::TargetTargetDestroyed(ref ev) => self.on_target_destroyed(ev.clone()),
423 CdpEvent::TargetDetachedFromTarget(ref ev) => self.on_detached_from_target(ev.clone()),
424 _ => {}
425 }
426
427 chromiumoxide_cdp::consume_event!(match params {
428 |ev| self.event_listeners.start_send(ev),
429 |json| { let _ = self.event_listeners.try_send_custom(&method, json);}
430 });
431 }
432
433 fn on_target_created(&mut self, event: EventTargetCreated) {
437 let browser_ctx = match event.target_info.browser_context_id {
438 Some(ref context_id) => {
439 let browser_context = BrowserContext {
440 id: Some(context_id.clone()),
441 };
442 if self.default_browser_context.id.is_none() {
443 self.default_browser_context = browser_context.clone();
444 };
445 self.browser_contexts.insert(browser_context.clone());
446
447 browser_context
448 }
449 _ => event
450 .target_info
451 .browser_context_id
452 .clone()
453 .map(BrowserContext::from)
454 .filter(|id| self.browser_contexts.contains(id))
455 .unwrap_or_else(|| self.default_browser_context.clone()),
456 };
457
458 let target = Target::new(
459 event.target_info,
460 TargetConfig {
461 ignore_https_errors: self.config.ignore_https_errors,
462 request_timeout: self.config.request_timeout,
463 viewport: self.config.viewport.clone(),
464 request_intercept: self.config.request_intercept,
465 cache_enabled: self.config.cache_enabled,
466 service_worker_enabled: self.config.service_worker_enabled,
467 ignore_visuals: self.config.ignore_visuals,
468 ignore_stylesheets: self.config.ignore_stylesheets,
469 ignore_javascript: self.config.ignore_javascript,
470 ignore_analytics: self.config.ignore_analytics,
471 extra_headers: self.config.extra_headers.clone(),
472 only_html: self.config.only_html && self.config.created_first_target,
473 intercept_manager: self.config.intercept_manager,
474 },
475 browser_ctx,
476 );
477
478 self.target_ids.push(target.target_id().clone());
479 self.targets.insert(target.target_id().clone(), target);
480 }
481
482 fn on_attached_to_target(&mut self, event: Box<EventAttachedToTarget>) {
484 let session = Session::new(event.session_id.clone(), event.target_info.target_id);
485 if let Some(target) = self.targets.get_mut(session.target_id()) {
486 target.set_session_id(session.session_id().clone())
487 }
488 self.sessions.insert(event.session_id, session);
489 }
490
491 fn on_detached_from_target(&mut self, event: EventDetachedFromTarget) {
495 if let Some(session) = self.sessions.remove(&event.session_id) {
497 if let Some(target) = self.targets.get_mut(session.target_id()) {
498 target.session_id().take();
499 }
500 }
501 }
502
503 fn on_target_destroyed(&mut self, event: EventTargetDestroyed) {
505 if let Some(target) = self.targets.remove(&event.target_id) {
506 if let Some(session) = target.session_id() {
508 self.sessions.remove(session);
509 }
510 }
511 }
512
513 fn evict_timed_out_commands(&mut self, now: Instant) {
518 let timed_out = self
519 .pending_commands
520 .iter()
521 .filter(|(_, (_, _, timestamp))| now > (*timestamp + self.config.request_timeout))
522 .map(|(k, _)| *k)
523 .collect::<Vec<_>>();
524
525 for call in timed_out {
526 if let Some((req, _, _)) = self.pending_commands.remove(&call) {
527 match req {
528 PendingRequest::CreateTarget(tx) => {
529 let _ = tx.send(Err(CdpError::Timeout));
530 }
531 PendingRequest::GetTargets(tx) => {
532 let _ = tx.send(Err(CdpError::Timeout));
533 }
534 PendingRequest::Navigate(nav) => {
535 if let Some(nav) = self.navigations.remove(&nav) {
536 match nav {
537 NavigationRequest::Navigate(nav) => {
538 let _ = nav.tx.send(Err(CdpError::Timeout));
539 }
540 }
541 }
542 }
543 PendingRequest::ExternalCommand(tx) => {
544 let _ = tx.send(Err(CdpError::Timeout));
545 }
546 PendingRequest::InternalCommand(_) => {}
547 PendingRequest::CloseBrowser(tx) => {
548 let _ = tx.send(Err(CdpError::Timeout));
549 }
550 }
551 }
552 }
553 }
554
555 pub fn event_listeners_mut(&mut self) -> &mut EventListeners {
556 &mut self.event_listeners
557 }
558}
559
560impl Stream for Handler {
561 type Item = Result<()>;
562
563 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
564 let pin = self.get_mut();
565
566 let mut dispose = false;
567
568 loop {
569 let now = Instant::now();
570 while let Poll::Ready(Some(msg)) = Pin::new(&mut pin.from_browser).poll_next(cx) {
574 match msg {
575 HandlerMessage::Command(cmd) => {
576 pin.submit_external_command(cmd, now)?;
577 }
578 HandlerMessage::FetchTargets(tx) => {
579 pin.submit_fetch_targets(tx, now);
580 }
581 HandlerMessage::CloseBrowser(tx) => {
582 pin.submit_close(tx, now);
583 }
584 HandlerMessage::CreatePage(params, tx) => {
585 pin.create_page(params, tx);
586 }
587 HandlerMessage::GetPages(tx) => {
588 let pages: Vec<_> = pin
589 .targets
590 .values_mut()
591 .filter(|p: &&mut Target| p.is_page())
592 .filter_map(|target| target.get_or_create_page())
593 .map(|page| Page::from(page.clone()))
594 .collect();
595 let _ = tx.send(pages);
596 }
597 HandlerMessage::InsertContext(ctx) => {
598 pin.default_browser_context = ctx.clone();
599 pin.browser_contexts.insert(ctx);
600 }
601 HandlerMessage::DisposeContext(ctx) => {
602 pin.browser_contexts.remove(&ctx);
603 pin.closing = true;
604 dispose = true;
605 }
606 HandlerMessage::GetPage(target_id, tx) => {
607 let page = pin
608 .targets
609 .get_mut(&target_id)
610 .and_then(|target| target.get_or_create_page())
611 .map(|page| Page::from(page.clone()));
612 let _ = tx.send(page);
613 }
614 HandlerMessage::AddEventListener(req) => {
615 pin.event_listeners.add_listener(req);
616 }
617 }
618 }
619
620 for n in (0..pin.target_ids.len()).rev() {
621 let target_id = pin.target_ids.swap_remove(n);
622
623 if let Some((id, mut target)) = pin.targets.remove_entry(&target_id) {
624 while let Some(event) = target.poll(cx, now) {
625 match event {
626 TargetEvent::Request(req) => {
627 let _ = pin.submit_internal_command(
628 target.target_id().clone(),
629 req,
630 now,
631 );
632 }
633 TargetEvent::Command(msg) => {
634 pin.on_target_message(&mut target, msg, now);
635 }
636 TargetEvent::NavigationRequest(id, req) => {
637 pin.submit_navigation(id, req, now);
638 }
639 TargetEvent::NavigationResult(res) => {
640 pin.on_navigation_lifecycle_completed(res)
641 }
642 }
643 }
644
645 target.event_listeners_mut().poll(cx);
647 pin.event_listeners_mut().poll(cx);
649
650 pin.targets.insert(id, target);
651 pin.target_ids.push(target_id);
652 }
653 }
654
655 let mut done = true;
656
657 while let Poll::Ready(Some(ev)) = Pin::new(&mut pin.conn).poll_next(cx) {
658 match ev {
659 Ok(Message::Response(resp)) => {
660 pin.on_response(resp);
661 if pin.closing {
662 return Poll::Ready(None);
664 }
665 }
666 Ok(Message::Event(ev)) => {
667 pin.on_event(ev);
668 }
669 Err(err) => {
670 tracing::error!("WS Connection error: {:?}", err);
671 match err {
672 CdpError::Ws(ref ws_error) => match ws_error {
673 Error::AlreadyClosed => {
674 pin.closing = true;
675 dispose = true;
676 break;
677 }
678 Error::Protocol(detail)
679 if detail == &ProtocolError::ResetWithoutClosingHandshake =>
680 {
681 pin.closing = true;
682 dispose = true;
683 break;
684 }
685 _ => {}
686 },
687 _ => {}
688 };
689 return Poll::Ready(Some(Err(err)));
690 }
691 }
692 done = false;
693 }
694
695 if pin.evict_command_timeout.poll_ready(cx) {
696 pin.evict_timed_out_commands(now);
698 }
699
700 if dispose {
701 return Poll::Ready(None);
702 }
703
704 if done {
705 return Poll::Pending;
707 }
708 }
709 }
710}
711
712#[derive(Debug, Clone)]
714pub struct HandlerConfig {
715 pub ignore_https_errors: bool,
717 pub viewport: Option<Viewport>,
719 pub context_ids: Vec<BrowserContextId>,
721 pub request_timeout: Duration,
723 pub request_intercept: bool,
725 pub cache_enabled: bool,
727 pub service_worker_enabled: bool,
729 pub ignore_visuals: bool,
731 pub ignore_stylesheets: bool,
733 pub ignore_javascript: bool,
735 pub ignore_analytics: bool,
737 pub ignore_ads: bool,
739 pub extra_headers: Option<std::collections::HashMap<String, String>>,
741 pub only_html: bool,
743 pub created_first_target: bool,
745 pub intercept_manager: NetworkInterceptManager,
747}
748
749impl Default for HandlerConfig {
750 fn default() -> Self {
751 Self {
752 ignore_https_errors: true,
753 viewport: Default::default(),
754 context_ids: Vec::new(),
755 request_timeout: Duration::from_millis(REQUEST_TIMEOUT),
756 request_intercept: false,
757 cache_enabled: true,
758 service_worker_enabled: true,
759 ignore_visuals: false,
760 ignore_stylesheets: false,
761 ignore_ads: false,
762 ignore_javascript: false,
763 ignore_analytics: true,
764 only_html: false,
765 extra_headers: Default::default(),
766 created_first_target: false,
767 intercept_manager: NetworkInterceptManager::Unknown,
768 }
769 }
770}
771
772#[derive(Debug)]
774pub struct NavigationInProgress<T> {
775 navigated: bool,
777 response: Option<Response>,
779 tx: OneshotSender<T>,
781}
782
783impl<T> NavigationInProgress<T> {
784 fn new(tx: OneshotSender<T>) -> Self {
785 Self {
786 navigated: false,
787 response: None,
788 tx,
789 }
790 }
791
792 fn set_response(&mut self, resp: Response) {
794 self.response = Some(resp);
795 }
796
797 fn set_navigated(&mut self) {
799 self.navigated = true;
800 }
801}
802
803#[derive(Debug)]
805enum NavigationRequest {
806 Navigate(NavigationInProgress<Result<Response>>),
808 }
810
811#[derive(Debug)]
814enum PendingRequest {
815 CreateTarget(OneshotSender<Result<Page>>),
818 GetTargets(OneshotSender<Result<Vec<TargetInfo>>>),
820 Navigate(NavigationId),
827 ExternalCommand(OneshotSender<Result<Response>>),
829 InternalCommand(TargetId),
832 CloseBrowser(OneshotSender<Result<CloseReturns>>),
834}
835
836#[derive(Debug)]
840pub(crate) enum HandlerMessage {
841 CreatePage(CreateTargetParams, OneshotSender<Result<Page>>),
842 FetchTargets(OneshotSender<Result<Vec<TargetInfo>>>),
843 InsertContext(BrowserContext),
844 DisposeContext(BrowserContext),
845 GetPages(OneshotSender<Vec<Page>>),
846 Command(CommandMessage),
847 GetPage(TargetId, OneshotSender<Option<Page>>),
848 AddEventListener(EventListenerRequest),
849 CloseBrowser(OneshotSender<Result<CloseReturns>>),
850}