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(&self, options: Option<WebSocketRouteCloseOptions>) -> Result<()> {
146 let opts = options.unwrap_or_default();
147 let mut params = serde_json::Map::new();
148 if let Some(code) = opts.code {
149 params.insert("code".to_string(), serde_json::json!(code));
150 }
151 if let Some(reason) = opts.reason {
152 params.insert("reason".to_string(), serde_json::json!(reason));
153 }
154 self.base
155 .channel()
156 .send_no_result("close", Value::Object(params))
157 .await
158 }
159
160 /// Sends a text message to the page.
161 ///
162 /// # Arguments
163 ///
164 /// * `message` — The text message to send.
165 ///
166 /// See: <https://playwright.dev/docs/api/class-websocketroute#web-socket-route-send>
167 pub async fn send(&self, message: &str) -> Result<()> {
168 self.base
169 .channel()
170 .send_no_result(
171 "sendToPage",
172 serde_json::json!({ "message": message, "isBase64": false }),
173 )
174 .await
175 }
176
177 /// Registers a handler for messages sent from the page.
178 ///
179 /// # Arguments
180 ///
181 /// * `handler` — Async closure that receives the message payload as a `String`.
182 ///
183 /// See: <https://playwright.dev/docs/api/class-websocketroute#web-socket-route-on-message>
184 pub async fn on_message<F>(&self, handler: F) -> Result<()>
185 where
186 F: Fn(String) -> WebSocketRouteHandlerFuture + Send + Sync + 'static,
187 {
188 let handler_arc = Arc::new(handler);
189 self.message_handlers.lock().unwrap().push(handler_arc);
190 Ok(())
191 }
192
193 /// Registers a handler for when the WebSocket is closed by the page.
194 ///
195 /// See: <https://playwright.dev/docs/api/class-websocketroute#web-socket-route-on-close>
196 pub async fn on_close<F>(&self, handler: F) -> Result<()>
197 where
198 F: Fn() -> WebSocketRouteHandlerFuture + Send + Sync + 'static,
199 {
200 let handler_arc = Arc::new(handler);
201 self.close_handlers.lock().unwrap().push(handler_arc);
202 Ok(())
203 }
204
205 /// Dispatches an incoming server-side event to registered handlers.
206 pub(crate) fn handle_event(&self, event: &str, params: &Value) {
207 match event {
208 "messageFromPage" => {
209 let payload = params["message"].as_str().unwrap_or("").to_string();
210 let handlers = self.message_handlers.lock().unwrap().clone();
211 for handler in handlers {
212 let p = payload.clone();
213 tokio::spawn(async move {
214 let _ = handler(p).await;
215 });
216 }
217 }
218 "close" => {
219 let handlers = self.close_handlers.lock().unwrap().clone();
220 for handler in handlers {
221 tokio::spawn(async move {
222 let _ = handler().await;
223 });
224 }
225 }
226 _ => {}
227 }
228 }
229}
230
231/// Options for [`WebSocketRoute::close`].
232#[derive(Debug, Default, Clone)]
233#[non_exhaustive]
234pub struct WebSocketRouteCloseOptions {
235 /// WebSocket close code (e.g. 1000 for normal closure).
236 pub code: Option<u16>,
237 /// Human-readable close reason.
238 pub reason: Option<String>,
239}
240
241impl ChannelOwner for WebSocketRoute {
242 fn guid(&self) -> &str {
243 self.base.guid()
244 }
245
246 fn type_name(&self) -> &str {
247 self.base.type_name()
248 }
249
250 fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
251 self.base.parent()
252 }
253
254 fn connection(&self) -> Arc<dyn crate::server::connection::ConnectionLike> {
255 self.base.connection()
256 }
257
258 fn initializer(&self) -> &Value {
259 self.base.initializer()
260 }
261
262 fn channel(&self) -> &Channel {
263 self.base.channel()
264 }
265
266 fn dispose(&self, reason: crate::server::channel_owner::DisposeReason) {
267 self.base.dispose(reason)
268 }
269
270 fn adopt(&self, child: Arc<dyn ChannelOwner>) {
271 self.base.adopt(child)
272 }
273
274 fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
275 self.base.add_child(guid, child)
276 }
277
278 fn remove_child(&self, guid: &str) {
279 self.base.remove_child(guid)
280 }
281
282 fn on_event(&self, method: &str, params: Value) {
283 self.handle_event(method, ¶ms);
284 self.base.on_event(method, params)
285 }
286
287 fn was_collected(&self) -> bool {
288 self.base.was_collected()
289 }
290
291 fn as_any(&self) -> &dyn Any {
292 self
293 }
294}