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, path::PathBuf, sync::Arc};
8
9use tempfile::TempDir;
10use tokio::sync::{Mutex, RwLock};
11use void_crawl_core::{BrowserSession, DownloadCapture, Page};
12
13pub type SessionId = String;
14
15/// A download armed on a session by `download_arm`, awaiting `download_wait`.
16/// Holds the quarantine `TempDir` alive between the two tool calls.
17#[derive(Debug)]
18pub struct PendingDownload {
19    pub capture:    DownloadCapture,
20    pub quarantine: TempDir,
21    pub output_dir: PathBuf,
22    pub max_bytes:  u64,
23}
24
25/// Owned state for one stateful MCP session.
26#[derive(Debug)]
27pub struct DedicatedSession {
28    pub session:          Arc<BrowserSession>,
29    pub page:             Mutex<Page>,
30    /// A download armed via `download_arm`, pending its `download_wait`.
31    pub pending_download: Mutex<Option<PendingDownload>>,
32}
33
34/// Thread-safe map of live sessions.
35#[derive(Debug, Default)]
36pub struct SessionRegistry {
37    inner: RwLock<HashMap<SessionId, Arc<DedicatedSession>>>,
38}
39
40impl SessionRegistry {
41    pub async fn insert(&self, id: SessionId, session: Arc<DedicatedSession>) {
42        self.inner.write().await.insert(id, session);
43    }
44
45    pub async fn get(&self, id: &str) -> Option<Arc<DedicatedSession>> {
46        self.inner.read().await.get(id).cloned()
47    }
48
49    pub async fn remove(&self, id: &str) -> Option<Arc<DedicatedSession>> {
50        self.inner.write().await.remove(id)
51    }
52
53    pub async fn len(&self) -> usize {
54        self.inner.read().await.len()
55    }
56
57    pub async fn is_empty(&self) -> bool {
58        self.inner.read().await.is_empty()
59    }
60
61    /// Drain every session. Used on shutdown.
62    pub async fn drain(&self) -> Vec<Arc<DedicatedSession>> {
63        self.inner.write().await.drain().map(|(_, v)| v).collect()
64    }
65}