Skip to main content

allwright/
client_browser.rs

1use 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}