viewpoint_core/context/routing/
mod.rs1use std::future::Future;
10use std::pin::Pin;
11use std::sync::{Arc, Weak};
12
13use tokio::sync::{RwLock, broadcast};
14use tracing::debug;
15
16use viewpoint_cdp::CdpConnection;
17
18use crate::error::NetworkError;
19use crate::network::{Route, RouteHandlerRegistry, UrlMatcher, UrlPattern};
20
21#[derive(Debug, Clone)]
23pub enum RouteChangeNotification {
24 RouteAdded,
26}
27
28struct ContextRouteHandler {
30 pattern: Box<dyn UrlMatcher>,
32 handler: Arc<
34 dyn Fn(Route) -> Pin<Box<dyn Future<Output = Result<(), NetworkError>> + Send>>
35 + Send
36 + Sync,
37 >,
38}
39
40impl std::fmt::Debug for ContextRouteHandler {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 f.debug_struct("ContextRouteHandler")
43 .field("pattern", &"<pattern>")
44 .field("handler", &"<fn>")
45 .finish()
46 }
47}
48
49#[derive(Debug)]
54pub struct ContextRouteRegistry {
55 handlers: RwLock<Vec<ContextRouteHandler>>,
57 connection: Arc<CdpConnection>,
59 context_id: String,
61 route_change_tx: broadcast::Sender<RouteChangeNotification>,
63 page_registries: RwLock<Vec<Weak<RouteHandlerRegistry>>>,
66}
67
68impl ContextRouteRegistry {
69 pub fn new(connection: Arc<CdpConnection>, context_id: String) -> Self {
71 let (route_change_tx, _) = broadcast::channel(16);
73 Self {
74 handlers: RwLock::new(Vec::new()),
75 connection,
76 context_id,
77 route_change_tx,
78 page_registries: RwLock::new(Vec::new()),
79 }
80 }
81
82 pub async fn register_page_registry(&self, registry: &Arc<RouteHandlerRegistry>) {
87 let mut registries = self.page_registries.write().await;
88 registries.retain(|weak| weak.strong_count() > 0);
90 registries.push(Arc::downgrade(registry));
91 }
92
93 async fn enable_fetch_on_all_pages(&self) -> Result<(), NetworkError> {
97 let registries = self.page_registries.read().await;
98 for weak in registries.iter() {
99 if let Some(registry) = weak.upgrade() {
100 registry.ensure_fetch_enabled_public().await?;
101 }
102 }
103 Ok(())
104 }
105
106 pub fn subscribe_route_changes(&self) -> broadcast::Receiver<RouteChangeNotification> {
111 self.route_change_tx.subscribe()
112 }
113
114 pub async fn route<M, H, Fut>(&self, pattern: M, handler: H) -> Result<(), NetworkError>
122 where
123 M: Into<UrlPattern>,
124 H: Fn(Route) -> Fut + Send + Sync + 'static,
125 Fut: Future<Output = Result<(), NetworkError>> + Send + 'static,
126 {
127 let pattern = pattern.into();
128
129 debug!(context_id = %self.context_id, "Registering context route");
130
131 let handler: Arc<
133 dyn Fn(Route) -> Pin<Box<dyn Future<Output = Result<(), NetworkError>> + Send>>
134 + Send
135 + Sync,
136 > = Arc::new(move |route| Box::pin(handler(route)));
137
138 let mut handlers = self.handlers.write().await;
140 handlers.push(ContextRouteHandler {
141 pattern: Box::new(pattern),
142 handler,
143 });
144 drop(handlers); self.enable_fetch_on_all_pages().await?;
150
151 let _ = self
154 .route_change_tx
155 .send(RouteChangeNotification::RouteAdded);
156
157 Ok(())
158 }
159
160 pub async fn route_predicate<P, H, Fut>(
166 &self,
167 predicate: P,
168 handler: H,
169 ) -> Result<(), NetworkError>
170 where
171 P: Fn(&str) -> bool + Send + Sync + 'static,
172 H: Fn(Route) -> Fut + Send + Sync + 'static,
173 Fut: Future<Output = Result<(), NetworkError>> + Send + 'static,
174 {
175 struct PredicateMatcher<F>(F);
177 impl<F: Fn(&str) -> bool + Send + Sync> UrlMatcher for PredicateMatcher<F> {
178 fn matches(&self, url: &str) -> bool {
179 (self.0)(url)
180 }
181 }
182
183 let handler: Arc<
185 dyn Fn(Route) -> Pin<Box<dyn Future<Output = Result<(), NetworkError>> + Send>>
186 + Send
187 + Sync,
188 > = Arc::new(move |route| Box::pin(handler(route)));
189
190 let mut handlers = self.handlers.write().await;
192 handlers.push(ContextRouteHandler {
193 pattern: Box::new(PredicateMatcher(predicate)),
194 handler,
195 });
196 drop(handlers); self.enable_fetch_on_all_pages().await?;
200
201 let _ = self
203 .route_change_tx
204 .send(RouteChangeNotification::RouteAdded);
205
206 Ok(())
207 }
208
209 pub async fn unroute(&self, pattern: &str) {
211 let mut handlers = self.handlers.write().await;
212 handlers.retain(|h| !h.pattern.matches(pattern));
213 }
214
215 pub async fn unroute_all(&self) {
217 let mut handlers = self.handlers.write().await;
218 handlers.clear();
219 }
220
221 pub async fn has_routes(&self) -> bool {
223 let handlers = self.handlers.read().await;
224 !handlers.is_empty()
225 }
226
227 pub async fn route_count(&self) -> usize {
229 let handlers = self.handlers.read().await;
230 handlers.len()
231 }
232
233 #[deprecated(note = "Use set_context_routes on RouteHandlerRegistry instead")]
242 pub async fn apply_to_page(
243 &self,
244 page_registry: &RouteHandlerRegistry,
245 ) -> Result<(), NetworkError> {
246 let handlers = self.handlers.read().await;
247
248 for handler in handlers.iter() {
249 let handler_clone = handler.handler.clone();
251
252 page_registry
260 .route("*", move |route| {
261 let handler = handler_clone.clone();
262 async move { handler(route).await }
263 })
264 .await?;
265 }
266
267 Ok(())
268 }
269
270 pub async fn find_handler(
275 &self,
276 url: &str,
277 ) -> Option<
278 Arc<
279 dyn Fn(Route) -> Pin<Box<dyn Future<Output = Result<(), NetworkError>> + Send>>
280 + Send
281 + Sync,
282 >,
283 > {
284 let handlers = self.handlers.read().await;
285
286 for handler in handlers.iter().rev() {
288 if handler.pattern.matches(url) {
289 return Some(handler.handler.clone());
290 }
291 }
292
293 None
294 }
295
296 pub async fn find_all_handlers(
302 &self,
303 url: &str,
304 ) -> Vec<
305 Arc<
306 dyn Fn(Route) -> Pin<Box<dyn Future<Output = Result<(), NetworkError>> + Send>>
307 + Send
308 + Sync,
309 >,
310 > {
311 let handlers = self.handlers.read().await;
312
313 handlers
315 .iter()
316 .rev()
317 .filter(|h| h.pattern.matches(url))
318 .map(|h| h.handler.clone())
319 .collect()
320 }
321}
322
323#[cfg(test)]
324mod tests;