Skip to main content

allwright/
client_launch.rs

1use std::sync::Arc;
2
3use crate::proto::browser_session_command::Command as BrowserCommand;
4use crate::proto::browser_session_event::Event as BrowserEvent;
5use crate::proto::{
6    BrowserKind as ProtoBrowserKind, BrowserLaunchedEvent, BrowserSessionCommand,
7    LaunchBrowserCommand,
8};
9use tokio::sync::{Mutex as AsyncMutex, mpsc};
10use tokio_stream::wrappers::ReceiverStream;
11
12use super::command::command_retry_options;
13use super::runtime::get_runtime;
14use super::types::{
15    Browser, BrowserInner, BrowserKind, BrowserState, BrowserType, Error, LaunchOptions, Result,
16    Tab, TabInner, TabState,
17};
18
19pub async fn launch_chrome(options: LaunchOptions) -> Result<Browser> {
20    launch_browser(BrowserKind::Chromium, options).await
21}
22
23pub async fn launch_firefox(options: LaunchOptions) -> Result<Browser> {
24    launch_browser(BrowserKind::Firefox, options).await
25}
26
27pub async fn launch_browser(browser_kind: BrowserKind, options: LaunchOptions) -> Result<Browser> {
28    let runtime = get_runtime().await?;
29    let mut engine = runtime.engine.clone();
30    let (command_tx, command_rx) = mpsc::channel(16);
31    let response = engine
32        .browser_session(tonic::Request::new(ReceiverStream::new(command_rx)))
33        .await?;
34    let mut events = response.into_inner();
35
36    command_tx
37        .send(BrowserSessionCommand {
38            command: Some(BrowserCommand::LaunchBrowser(LaunchBrowserCommand {
39                browser_kind: match browser_kind {
40                    BrowserKind::Chromium => ProtoBrowserKind::Chromium as i32,
41                    BrowserKind::Firefox => ProtoBrowserKind::Firefox as i32,
42                },
43                browser_binary: options.browser_binary,
44                retry_options: command_retry_options(options.timeout_ms),
45            })),
46        })
47        .await
48        .map_err(|_| Error::new("failed to send LaunchBrowserCommand to browser session"))?;
49
50    loop {
51        let event = events
52            .message()
53            .await?
54            .ok_or_else(|| Error::new("browser session closed before launch response"))?;
55
56        match event.event {
57            Some(BrowserEvent::BrowserLaunched(BrowserLaunchedEvent {
58                browser,
59                note,
60                user_data_dir,
61                initial_tab_session_id,
62                ..
63            })) => {
64                return Ok(build_browser(
65                    runtime,
66                    command_tx,
67                    events,
68                    event.session_id,
69                    browser,
70                    note,
71                    String::new(),
72                    user_data_dir,
73                    initial_tab_session_id,
74                ));
75            }
76            Some(BrowserEvent::ChromeLaunched(launched)) => {
77                return Ok(build_browser(
78                    runtime,
79                    command_tx,
80                    events,
81                    event.session_id,
82                    launched.browser,
83                    launched.note,
84                    launched.cdp_websocket_url,
85                    launched.user_data_dir,
86                    launched.initial_tab_session_id,
87                ));
88            }
89            Some(BrowserEvent::Error(error)) => {
90                return Err(Error::new(format!(
91                    "browser session error during launch: {}",
92                    error.message
93                )));
94            }
95            _ => {}
96        }
97    }
98}
99
100pub fn chromium() -> BrowserType {
101    BrowserType {
102        browser_kind: BrowserKind::Chromium,
103    }
104}
105
106pub fn firefox() -> BrowserType {
107    BrowserType {
108        browser_kind: BrowserKind::Firefox,
109    }
110}
111
112impl BrowserType {
113    pub async fn launch(&self, options: LaunchOptions) -> Result<Browser> {
114        launch_browser(self.browser_kind, options).await
115    }
116}
117
118fn build_browser(
119    runtime: Arc<super::types::RuntimeClient>,
120    command_tx: mpsc::Sender<BrowserSessionCommand>,
121    events: tonic::Streaming<crate::proto::BrowserSessionEvent>,
122    browser_session_id: String,
123    browser: String,
124    note: String,
125    cdp_websocket_url: String,
126    user_data_dir: String,
127    initial_tab_session_id: String,
128) -> Browser {
129    let initial_tab = Tab {
130        inner: Arc::new(TabInner {
131            runtime: Arc::clone(&runtime),
132            browser_session_id: browser_session_id.clone(),
133            session_id: initial_tab_session_id,
134            state: AsyncMutex::new(TabState::default()),
135        }),
136    };
137    Browser {
138        inner: Arc::new(BrowserInner {
139            runtime,
140            state: AsyncMutex::new(BrowserState {
141                command_tx,
142                events,
143                closed: false,
144            }),
145            session_id: browser_session_id,
146            browser_name: browser,
147            launch_note: note,
148            cdp_websocket_url,
149            user_data_dir,
150            initial_tab,
151        }),
152    }
153}