vs_daemon/daemon/
lifecycle.rs1use 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 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}