viewpoint_core/context/routing/
mod.rs1use std::future::Future;
10use std::pin::Pin;
11use std::sync::{Arc, Weak};
12
13use tokio::sync::{broadcast, RwLock};
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.route_change_tx.send(RouteChangeNotification::RouteAdded);
154
155 Ok(())
156 }
157
158 pub async fn route_predicate<P, H, Fut>(&self, predicate: P, handler: H) -> Result<(), NetworkError>
164 where
165 P: Fn(&str) -> bool + Send + Sync + 'static,
166 H: Fn(Route) -> Fut + Send + Sync + 'static,
167 Fut: Future<Output = Result<(), NetworkError>> + Send + 'static,
168 {
169 struct PredicateMatcher<F>(F);
171 impl<F: Fn(&str) -> bool + Send + Sync> UrlMatcher for PredicateMatcher<F> {
172 fn matches(&self, url: &str) -> bool {
173 (self.0)(url)
174 }
175 }
176
177 let handler: Arc<
179 dyn Fn(Route) -> Pin<Box<dyn Future<Output = Result<(), NetworkError>> + Send>>
180 + Send
181 + Sync,
182 > = Arc::new(move |route| Box::pin(handler(route)));
183
184 let mut handlers = self.handlers.write().await;
186 handlers.push(ContextRouteHandler {
187 pattern: Box::new(PredicateMatcher(predicate)),
188 handler,
189 });
190 drop(handlers); self.enable_fetch_on_all_pages().await?;
194
195 let _ = self.route_change_tx.send(RouteChangeNotification::RouteAdded);
197
198 Ok(())
199 }
200
201 pub async fn unroute(&self, pattern: &str) {
203 let mut handlers = self.handlers.write().await;
204 handlers.retain(|h| !h.pattern.matches(pattern));
205 }
206
207 pub async fn unroute_all(&self) {
209 let mut handlers = self.handlers.write().await;
210 handlers.clear();
211 }
212
213 pub async fn has_routes(&self) -> bool {
215 let handlers = self.handlers.read().await;
216 !handlers.is_empty()
217 }
218
219 pub async fn route_count(&self) -> usize {
221 let handlers = self.handlers.read().await;
222 handlers.len()
223 }
224
225 #[deprecated(note = "Use set_context_routes on RouteHandlerRegistry instead")]
234 pub async fn apply_to_page(&self, page_registry: &RouteHandlerRegistry) -> Result<(), NetworkError> {
235 let handlers = self.handlers.read().await;
236
237 for handler in handlers.iter() {
238 let handler_clone = handler.handler.clone();
240
241 page_registry.route("*", move |route| {
249 let handler = handler_clone.clone();
250 async move {
251 handler(route).await
252 }
253 }).await?;
254 }
255
256 Ok(())
257 }
258
259 pub async fn find_handler(&self, url: &str) -> Option<Arc<
264 dyn Fn(Route) -> Pin<Box<dyn Future<Output = Result<(), NetworkError>> + Send>>
265 + Send
266 + Sync,
267 >> {
268 let handlers = self.handlers.read().await;
269
270 for handler in handlers.iter().rev() {
272 if handler.pattern.matches(url) {
273 return Some(handler.handler.clone());
274 }
275 }
276
277 None
278 }
279
280 pub async fn find_all_handlers(&self, url: &str) -> Vec<Arc<
286 dyn Fn(Route) -> Pin<Box<dyn Future<Output = Result<(), NetworkError>> + Send>>
287 + Send
288 + Sync,
289 >> {
290 let handlers = self.handlers.read().await;
291
292 handlers
294 .iter()
295 .rev()
296 .filter(|h| h.pattern.matches(url))
297 .map(|h| h.handler.clone())
298 .collect()
299 }
300}
301
302#[cfg(test)]
303mod tests;