Skip to main content

playwright_rs/protocol/
web_socket_route.rs

1//! WebSocketRoute protocol object — represents an intercepted WebSocket connection.
2//!
3//! `WebSocketRoute` is created by the Playwright server when a WebSocket connection
4//! matches a pattern registered via [`crate::protocol::Page::route_web_socket`] or
5//! [`crate::protocol::BrowserContext::route_web_socket`].
6//!
7//! # Example
8//!
9//! ```no_run
10//! use playwright_rs::protocol::Playwright;
11//!
12//! #[tokio::main]
13//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
14//!     let playwright = Playwright::launch().await?;
15//!     let browser = playwright.chromium().launch().await?;
16//!     let page = browser.new_page().await?;
17//!
18//!     // Intercept all WebSocket connections and proxy them to the real server
19//!     page.route_web_socket("ws://**", |route| {
20//!         Box::pin(async move {
21//!             route.connect_to_server().await?;
22//!             Ok(())
23//!         })
24//!     })
25//!     .await?;
26//!
27//!     browser.close().await?;
28//!     Ok(())
29//! }
30//! ```
31//!
32//! See: <https://playwright.dev/docs/api/class-websocketroute>
33
34use crate::error::Result;
35use crate::server::channel::Channel;
36use crate::server::channel_owner::{ChannelOwner, ChannelOwnerImpl, ParentOrConnection};
37use serde_json::Value;
38use std::any::Any;
39use std::future::Future;
40use std::pin::Pin;
41use std::sync::{Arc, Mutex};
42
43/// Represents an intercepted WebSocket connection.
44///
45/// `WebSocketRoute` is passed to handlers registered via
46/// [`crate::protocol::Page::route_web_socket`] or [`crate::protocol::BrowserContext::route_web_socket`].
47/// The handler must call [`connect_to_server`](WebSocketRoute::connect_to_server)
48/// to forward the connection to the real server, or [`close`](WebSocketRoute::close)
49/// to terminate it.
50///
51/// See: <https://playwright.dev/docs/api/class-websocketroute>
52#[derive(Clone)]
53pub struct WebSocketRoute {
54    base: ChannelOwnerImpl,
55    /// The WebSocket URL being intercepted.
56    url: String,
57    /// Message handlers registered via on_message().
58    message_handlers: Arc<Mutex<Vec<WebSocketRouteMessageHandler>>>,
59    /// Close handlers registered via on_close().
60    close_handlers: Arc<Mutex<Vec<WebSocketRouteCloseHandler>>>,
61}
62
63/// Type alias for boxed WebSocketRoute message handler future.
64type WebSocketRouteHandlerFuture = Pin<Box<dyn Future<Output = Result<()>> + Send>>;
65
66/// Message handler type.
67type WebSocketRouteMessageHandler =
68    Arc<dyn Fn(String) -> WebSocketRouteHandlerFuture + Send + Sync>;
69
70/// Close handler type.
71type WebSocketRouteCloseHandler = Arc<dyn Fn() -> WebSocketRouteHandlerFuture + Send + Sync>;
72
73impl WebSocketRoute {
74    /// Creates a new `WebSocketRoute` object.
75    pub fn new(
76        parent: Arc<dyn ChannelOwner>,
77        type_name: String,
78        guid: Arc<str>,
79        initializer: Value,
80    ) -> Result<Self> {
81        let url = initializer["url"].as_str().unwrap_or("").to_string();
82        let base = ChannelOwnerImpl::new(
83            ParentOrConnection::Parent(parent),
84            type_name,
85            guid,
86            initializer,
87        );
88        Ok(Self {
89            base,
90            url,
91            message_handlers: Arc::new(Mutex::new(Vec::new())),
92            close_handlers: Arc::new(Mutex::new(Vec::new())),
93        })
94    }
95
96    /// Returns the URL of the intercepted WebSocket connection.
97    ///
98    /// See: <https://playwright.dev/docs/api/class-websocketroute#web-socket-route-url>
99    pub fn url(&self) -> &str {
100        &self.url
101    }
102
103    /// Returns the WebSocket subprotocols the page requested (the
104    /// `Sec-WebSocket-Protocol` values) when opening this socket. Empty if none
105    /// were requested.
106    ///
107    /// See: <https://playwright.dev/docs/api/class-websocketroute#web-socket-route-protocols>
108    pub fn protocols(&self) -> Vec<String> {
109        self.base
110            .initializer()
111            .get("protocols")
112            .and_then(|v| v.as_array())
113            .map(|arr| {
114                arr.iter()
115                    .filter_map(|x| x.as_str().map(String::from))
116                    .collect()
117            })
118            .unwrap_or_default()
119    }
120
121    /// Connects this WebSocket to the actual server.
122    ///
123    /// After calling this method, all messages sent by the page are forwarded to
124    /// the server, and all messages sent by the server are forwarded to the page.
125    ///
126    /// # Errors
127    ///
128    /// Returns an error if the RPC call fails.
129    ///
130    /// See: <https://playwright.dev/docs/api/class-websocketroute#web-socket-route-connect-to-server>
131    pub async fn connect_to_server(&self) -> Result<()> {
132        self.base
133            .channel()
134            .send_no_result("connectToServer", serde_json::json!({}))
135            .await
136    }
137
138    /// Closes the WebSocket connection.
139    ///
140    /// # Arguments
141    ///
142    /// * `options` — Optional close code and reason.
143    ///
144    /// See: <https://playwright.dev/docs/api/class-websocketroute#web-socket-route-close>
145    pub async fn close(
146        &self,
147        options: impl Into<Option<WebSocketRouteCloseOptions>>,
148    ) -> Result<()> {
149        let options = options.into();
150        let opts = options.unwrap_or_default();
151        let mut params = serde_json::Map::new();
152        if let Some(code) = opts.code {
153            params.insert("code".to_string(), serde_json::json!(code));
154        }
155        if let Some(reason) = opts.reason {
156            params.insert("reason".to_string(), serde_json::json!(reason));
157        }
158        self.base
159            .channel()
160            .send_no_result("close", Value::Object(params))
161            .await
162    }
163
164    /// Sends a text message to the page.
165    ///
166    /// # Arguments
167    ///
168    /// * `message` — The text message to send.
169    ///
170    /// See: <https://playwright.dev/docs/api/class-websocketroute#web-socket-route-send>
171    pub async fn send(&self, message: &str) -> Result<()> {
172        self.base
173            .channel()
174            .send_no_result(
175                "sendToPage",
176                serde_json::json!({ "message": message, "isBase64": false }),
177            )
178            .await
179    }
180
181    /// Registers a handler for messages sent from the page.
182    ///
183    /// # Arguments
184    ///
185    /// * `handler` — Async closure that receives the message payload as a `String`.
186    ///
187    /// See: <https://playwright.dev/docs/api/class-websocketroute#web-socket-route-on-message>
188    pub async fn on_message<F>(&self, handler: F) -> Result<()>
189    where
190        F: Fn(String) -> WebSocketRouteHandlerFuture + Send + Sync + 'static,
191    {
192        let handler_arc = Arc::new(handler);
193        self.message_handlers.lock().unwrap().push(handler_arc);
194        Ok(())
195    }
196
197    /// Registers a handler for when the WebSocket is closed by the page.
198    ///
199    /// See: <https://playwright.dev/docs/api/class-websocketroute#web-socket-route-on-close>
200    pub async fn on_close<F>(&self, handler: F) -> Result<()>
201    where
202        F: Fn() -> WebSocketRouteHandlerFuture + Send + Sync + 'static,
203    {
204        let handler_arc = Arc::new(handler);
205        self.close_handlers.lock().unwrap().push(handler_arc);
206        Ok(())
207    }
208
209    /// Dispatches an incoming server-side event to registered handlers.
210    pub(crate) fn handle_event(&self, event: &str, params: &Value) {
211        match event {
212            "messageFromPage" => {
213                let payload = params["message"].as_str().unwrap_or("").to_string();
214                let handlers = self.message_handlers.lock().unwrap().clone();
215                for handler in handlers {
216                    let p = payload.clone();
217                    tokio::spawn(async move {
218                        let _ = handler(p).await;
219                    });
220                }
221            }
222            "close" => {
223                let handlers = self.close_handlers.lock().unwrap().clone();
224                for handler in handlers {
225                    tokio::spawn(async move {
226                        let _ = handler().await;
227                    });
228                }
229            }
230            _ => {}
231        }
232    }
233}
234
235/// Options for [`WebSocketRoute::close`].
236#[derive(Debug, Default, Clone)]
237#[non_exhaustive]
238pub struct WebSocketRouteCloseOptions {
239    /// WebSocket close code (e.g. 1000 for normal closure).
240    pub code: Option<u16>,
241    /// Human-readable close reason.
242    pub reason: Option<String>,
243}
244
245impl ChannelOwner for WebSocketRoute {
246    fn guid(&self) -> &str {
247        self.base.guid()
248    }
249
250    fn type_name(&self) -> &str {
251        self.base.type_name()
252    }
253
254    fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
255        self.base.parent()
256    }
257
258    fn connection(&self) -> Arc<dyn crate::server::connection::ConnectionLike> {
259        self.base.connection()
260    }
261
262    fn initializer(&self) -> &Value {
263        self.base.initializer()
264    }
265
266    fn channel(&self) -> &Channel {
267        self.base.channel()
268    }
269
270    fn dispose(&self, reason: crate::server::channel_owner::DisposeReason) {
271        self.base.dispose(reason)
272    }
273
274    fn adopt(&self, child: Arc<dyn ChannelOwner>) {
275        self.base.adopt(child)
276    }
277
278    fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
279        self.base.add_child(guid, child)
280    }
281
282    fn remove_child(&self, guid: &str) {
283        self.base.remove_child(guid)
284    }
285
286    fn on_event(&self, method: &str, params: Value) {
287        self.handle_event(method, &params);
288        self.base.on_event(method, params)
289    }
290
291    fn was_collected(&self) -> bool {
292        self.base.was_collected()
293    }
294
295    fn as_any(&self) -> &dyn Any {
296        self
297    }
298}