allwright/
client_browser.rs1use std::sync::Arc;
2
3use crate::proto::surface_session_command::Command as SurfaceCommand;
4use crate::proto::surface_session_event::Event as SurfaceEvent;
5use crate::proto::{
6 CloseSurfaceSessionCommand, OpenContextCommand, SessionPingCommand, SurfaceSessionCommand,
7};
8use tokio::sync::Mutex as AsyncMutex;
9
10use super::command::command_retry_options;
11use super::types::{
12 Browser, BrowserState, CommandOptions, Error, Page, Result, Tab, TabInner, TabState,
13};
14
15impl Browser {
16 pub fn page(&self) -> Page {
17 self.initial_tab()
18 }
19
20 pub fn initial_page(&self) -> Page {
21 self.initial_tab()
22 }
23
24 pub fn session_id(&self) -> &str {
25 &self.inner.session_id
26 }
27
28 pub fn browser_name(&self) -> &str {
29 &self.inner.browser_name
30 }
31
32 pub fn launch_note(&self) -> &str {
33 &self.inner.launch_note
34 }
35
36 pub fn cdp_websocket_url(&self) -> &str {
37 &self.inner.cdp_websocket_url
38 }
39
40 pub fn user_data_dir(&self) -> &str {
41 &self.inner.user_data_dir
42 }
43
44 pub fn initial_tab(&self) -> Tab {
45 self.inner.initial_tab.clone()
46 }
47
48 pub async fn new_tab(&self) -> Result<Tab> {
49 self.new_tab_with_options(CommandOptions::default()).await
50 }
51
52 pub async fn new_page(&self) -> Result<Page> {
53 self.new_tab().await
54 }
55
56 pub async fn new_tab_with_options(&self, options: CommandOptions) -> Result<Tab> {
57 let mut state = self.inner.state.lock().await;
58 ensure_browser_open(&state, &self.inner.session_id)?;
59
60 state
61 .command_tx
62 .send(SurfaceSessionCommand {
63 command: Some(SurfaceCommand::OpenContext(OpenContextCommand {
64 retry_options: command_retry_options(options.timeout_ms),
65 })),
66 })
67 .await
68 .map_err(|_| Error::new("failed to send OpenContextCommand to browser session"))?;
69
70 loop {
71 let event =
72 state.events.message().await?.ok_or_else(|| {
73 Error::new("browser session closed while waiting for new tab")
74 })?;
75
76 match event.event {
77 Some(SurfaceEvent::ContextOpened(opened)) => {
78 return Ok(Tab {
79 inner: Arc::new(TabInner {
80 runtime: Arc::clone(&self.inner.runtime),
81 surface_session_id: self.inner.session_id.clone(),
82 session_id: opened.context_session_id,
83 state: AsyncMutex::new(TabState::default()),
84 }),
85 });
86 }
87 Some(SurfaceEvent::Error(error)) => {
88 return Err(Error::new(format!(
89 "browser session error while opening tab: {}",
90 error.message
91 )));
92 }
93 _ => {}
94 }
95 }
96 }
97
98 pub async fn ping(&self, message: impl Into<String>) -> Result<String> {
99 let mut state = self.inner.state.lock().await;
100 ensure_browser_open(&state, &self.inner.session_id)?;
101
102 state
103 .command_tx
104 .send(SurfaceSessionCommand {
105 command: Some(SurfaceCommand::Ping(SessionPingCommand {
106 message: message.into(),
107 })),
108 })
109 .await
110 .map_err(|_| Error::new("failed to send SessionPingCommand to browser session"))?;
111
112 loop {
113 let event = state
114 .events
115 .message()
116 .await?
117 .ok_or_else(|| Error::new("browser session closed while waiting for pong"))?;
118
119 match event.event {
120 Some(SurfaceEvent::Pong(pong)) => return Ok(pong.message),
121 Some(SurfaceEvent::Error(error)) => {
122 return Err(Error::new(format!(
123 "browser session error while pinging: {}",
124 error.message
125 )));
126 }
127 _ => {}
128 }
129 }
130 }
131
132 pub async fn close(&self) -> Result<()> {
133 let mut state = self.inner.state.lock().await;
134 if state.closed {
135 return Ok(());
136 }
137
138 state
139 .command_tx
140 .send(SurfaceSessionCommand {
141 command: Some(SurfaceCommand::Close(CloseSurfaceSessionCommand {})),
142 })
143 .await
144 .map_err(|_| Error::new("failed to send CloseSurfaceSessionCommand"))?;
145
146 loop {
147 let event =
148 state.events.message().await?.ok_or_else(|| {
149 Error::new("browser session closed before close confirmation")
150 })?;
151
152 match event.event {
153 Some(SurfaceEvent::Closed(_)) => {
154 state.closed = true;
155 return Ok(());
156 }
157 Some(SurfaceEvent::Error(error)) => {
158 return Err(Error::new(format!(
159 "browser session error while closing: {}",
160 error.message
161 )));
162 }
163 _ => {}
164 }
165 }
166 }
167}
168
169fn ensure_browser_open(state: &BrowserState, session_id: &str) -> Result<()> {
170 if state.closed {
171 return Err(Error::new(format!(
172 "browser session {} is closed",
173 session_id
174 )));
175 }
176 Ok(())
177}