Skip to main content

vs_daemon/daemon/
lifecycle.rs

1//! Session and page lifecycle: `vs_session_open`, `vs_session_close`,
2//! `vs_open`, `vs_close`.
3
4use vs_protocol::StateToken;
5
6use super::audit::AuditCtx;
7use super::responses::{CloseResponse, OpenResponse, SessionCloseResponse, SessionOpenResponse};
8use super::{short_id, Daemon, SessionState};
9use crate::error::{DaemonError, Result};
10use crate::page_state::PageState;
11use crate::{redact, tokens};
12
13impl Daemon {
14    /// Open a fresh session. Returns the new id and a synthetic empty
15    /// session-scoped state token (`0000…`); page-scoped tokens come
16    /// from `vs_open`.
17    pub fn session_open(&self, policy: Option<&str>) -> Result<SessionOpenResponse> {
18        let id = format!("s_{}", short_id());
19        let policy_args: Vec<String> = policy.into_iter().map(str::to_string).collect();
20        let ctx = AuditCtx::new("vs_session_open", &id).with_args(
21            redact::redact_args(
22                &[],
23                policy
24                    .map_or(vec![], |p| vec![("policy".into(), Some(p.into()))])
25                    .as_slice(),
26            ),
27            tokens::args_hash("vs_session_open", &policy_args),
28        );
29        self.audit_call(ctx, |ctx| {
30            ctx.after_token = Some(StateToken::ZERO);
31            let mut store = self.inner.store.lock().expect("poisoned");
32            let session = store.create_session(&id, policy)?;
33            drop(store);
34            self.inner
35                .sessions
36                .lock()
37                .expect("poisoned")
38                .insert(id.clone(), SessionState::new());
39            Ok(SessionOpenResponse {
40                session_id: session.id,
41                token: StateToken::ZERO,
42            })
43        })
44    }
45
46    pub fn session_close(&self, session_id: &str) -> Result<SessionCloseResponse> {
47        let ctx = AuditCtx::new("vs_session_close", session_id)
48            .with_args(String::new(), tokens::args_hash("vs_session_close", &[]));
49        self.audit_call(ctx, |_ctx| {
50            let page_handles: Vec<_> = {
51                let mut sessions = self.inner.sessions.lock().expect("poisoned");
52                let s = sessions
53                    .remove(session_id)
54                    .ok_or_else(|| DaemonError::UnknownSession(session_id.to_string()))?;
55                s.pages.into_values().map(|p| p.engine_handle).collect()
56            };
57            for h in page_handles {
58                let _ = self.inner.engine.close(h);
59            }
60            let mut store = self.inner.store.lock().expect("poisoned");
61            store.close_session(session_id)?;
62            for page in store.list_pages(session_id)? {
63                if page.closed_at.is_none() {
64                    let _ = store.close_page(&page.id);
65                }
66            }
67            Ok(SessionCloseResponse)
68        })
69    }
70
71    pub fn open(&self, session_id: &str, url: &str) -> Result<OpenResponse> {
72        let url_owned = url.to_string();
73        let ctx = AuditCtx::new("vs_open", session_id).with_args(
74            url_owned.clone(),
75            tokens::args_hash("vs_open", std::slice::from_ref(&url_owned)),
76        );
77        self.audit_call(ctx, |ctx| {
78            self.require_session(session_id)?;
79
80            let engine_handle = self.inner.engine.open(url)?;
81            let page_id = format!("p_{}", short_id());
82            ctx.page_id = Some(page_id.clone());
83
84            let mut store = self.inner.store.lock().expect("poisoned");
85            store.create_page(&page_id, session_id, &url_owned)?;
86            drop(store);
87
88            let tree = self.inner.engine.snapshot(engine_handle)?;
89            let token = tokens::compute(&tree, &url_owned, &page_id);
90            ctx.after_token = Some(token);
91
92            let mut page = PageState::new(page_id.clone(), url_owned.clone(), engine_handle);
93            page.last_tree = Some(tree.clone());
94            page.last_token = Some(token);
95            page.force_full = true;
96            for n in &tree {
97                page.seen_refs.insert(n.r);
98            }
99
100            let mut store = self.inner.store.lock().expect("poisoned");
101            store.update_page_token(&page_id, &token.to_string(), "engine", None)?;
102            drop(store);
103
104            self.inner
105                .sessions
106                .lock()
107                .expect("poisoned")
108                .get_mut(session_id)
109                .ok_or_else(|| DaemonError::UnknownSession(session_id.to_string()))?
110                .pages
111                .insert(page_id.clone(), page);
112
113            Ok(OpenResponse {
114                page_id,
115                token,
116                warnings: Vec::new(),
117            })
118        })
119    }
120
121    pub fn close(&self, session_id: &str, page_id: &str) -> Result<CloseResponse> {
122        let ctx = AuditCtx::new("vs_close", session_id)
123            .with_page(page_id)
124            .with_args(String::new(), tokens::args_hash("vs_close", &[]));
125        self.audit_call(ctx, |_ctx| {
126            let engine_handle = {
127                let mut sessions = self.inner.sessions.lock().expect("poisoned");
128                let session = sessions
129                    .get_mut(session_id)
130                    .ok_or_else(|| DaemonError::UnknownSession(session_id.to_string()))?;
131                let page = session
132                    .pages
133                    .remove(page_id)
134                    .ok_or_else(|| DaemonError::UnknownPage(page_id.to_string()))?;
135                page.engine_handle
136            };
137            let _ = self.inner.engine.close(engine_handle);
138            let mut store = self.inner.store.lock().expect("poisoned");
139            let _ = store.close_page(page_id);
140            Ok(CloseResponse)
141        })
142    }
143}