Skip to main content

voidcrawl_mcp/
sessions.rs

1//! Stateful session registry.
2//!
3//! Each `session_open` spawns a dedicated `BrowserSession` with its own
4//! temporary user-data-dir, so cookies and storage never leak between
5//! subagents. Pooled tabs are only used for stateless `fetch*` calls.
6
7use std::{collections::HashMap, sync::Arc};
8
9use tokio::sync::{Mutex, RwLock};
10use void_crawl_core::{BrowserSession, Page};
11
12pub type SessionId = String;
13
14/// Owned state for one stateful MCP session.
15#[derive(Debug)]
16pub struct DedicatedSession {
17    pub session: Arc<BrowserSession>,
18    pub page:    Mutex<Page>,
19}
20
21/// Thread-safe map of live sessions.
22#[derive(Debug, Default)]
23pub struct SessionRegistry {
24    inner: RwLock<HashMap<SessionId, Arc<DedicatedSession>>>,
25}
26
27impl SessionRegistry {
28    pub async fn insert(&self, id: SessionId, session: Arc<DedicatedSession>) {
29        self.inner.write().await.insert(id, session);
30    }
31
32    pub async fn get(&self, id: &str) -> Option<Arc<DedicatedSession>> {
33        self.inner.read().await.get(id).cloned()
34    }
35
36    pub async fn remove(&self, id: &str) -> Option<Arc<DedicatedSession>> {
37        self.inner.write().await.remove(id)
38    }
39
40    pub async fn len(&self) -> usize {
41        self.inner.read().await.len()
42    }
43
44    pub async fn is_empty(&self) -> bool {
45        self.inner.read().await.is_empty()
46    }
47
48    /// Drain every session. Used on shutdown.
49    pub async fn drain(&self) -> Vec<Arc<DedicatedSession>> {
50        self.inner.write().await.drain().map(|(_, v)| v).collect()
51    }
52}