browsertap_shared/
session.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4
5use crate::protocol::{ConsoleEvent, NetworkEvent, SocketState};
6
7pub const MAX_CONSOLE_BUFFER: usize = 500;
9
10pub const MAX_NETWORK_BUFFER: usize = 200;
12
13pub const HEARTBEAT_INTERVAL_SECS: u64 = 5;
15
16pub const HEARTBEAT_TIMEOUT_SECS: u64 = 45;
18
19#[derive(Debug, Clone)]
21pub struct Session {
22 pub id: Uuid,
23 pub codename: String,
24 pub url: String,
25 pub title: String,
26 pub user_agent: String,
27 pub top_origin: String,
28 pub socket_state: SocketState,
29 pub connected_at: DateTime<Utc>,
30 pub last_heartbeat: DateTime<Utc>,
31 pub console_buffer: Vec<ConsoleEvent>,
32 pub network_buffer: Vec<NetworkEvent>,
33}
34
35impl Session {
36 pub fn new(
37 id: Uuid,
38 codename: String,
39 url: String,
40 title: String,
41 user_agent: String,
42 top_origin: String,
43 ) -> Self {
44 let now = Utc::now();
45 Self {
46 id,
47 codename,
48 url,
49 title,
50 user_agent,
51 top_origin,
52 socket_state: SocketState::Open,
53 connected_at: now,
54 last_heartbeat: now,
55 console_buffer: Vec::new(),
56 network_buffer: Vec::new(),
57 }
58 }
59
60 pub fn is_stale(&self) -> bool {
62 let elapsed = Utc::now()
63 .signed_duration_since(self.last_heartbeat)
64 .num_seconds();
65 elapsed > HEARTBEAT_TIMEOUT_SECS as i64
66 }
67
68 pub fn touch(&mut self) {
70 self.last_heartbeat = Utc::now();
71 }
72
73 pub fn push_console_events(&mut self, events: Vec<ConsoleEvent>) {
75 self.console_buffer.extend(events);
76 if self.console_buffer.len() > MAX_CONSOLE_BUFFER {
77 let drain = self.console_buffer.len() - MAX_CONSOLE_BUFFER;
78 self.console_buffer.drain(..drain);
79 }
80 }
81
82 pub fn push_network_events(&mut self, events: Vec<NetworkEvent>) {
84 self.network_buffer.extend(events);
85 if self.network_buffer.len() > MAX_NETWORK_BUFFER {
86 let drain = self.network_buffer.len() - MAX_NETWORK_BUFFER;
87 self.network_buffer.drain(..drain);
88 }
89 }
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct DaemonConfig {
95 #[serde(default = "default_host")]
97 pub host: String,
98 #[serde(default = "default_port")]
100 pub port: u16,
101 #[serde(default)]
103 pub cert_path: Option<String>,
104 #[serde(default)]
106 pub key_path: Option<String>,
107}
108
109fn default_host() -> String {
110 "127.0.0.1".into()
111}
112
113fn default_port() -> u16 {
114 4455
115}
116
117impl Default for DaemonConfig {
118 fn default() -> Self {
119 Self {
120 host: default_host(),
121 port: default_port(),
122 cert_path: None,
123 key_path: None,
124 }
125 }
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize, Default)]
130pub struct ProjectConfig {
131 #[serde(default)]
132 pub app_label: Option<String>,
133 #[serde(default)]
134 pub app_url: Option<String>,
135 #[serde(default)]
136 pub daemon_url: Option<String>,
137 #[serde(default)]
138 pub daemon: DaemonConfig,
139 #[serde(default)]
140 pub smoke: SmokeConfig,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize, Default)]
145pub struct SmokeConfig {
146 #[serde(default)]
148 pub defaults: Vec<String>,
149 #[serde(default)]
151 pub presets: std::collections::HashMap<String, Vec<String>>,
152 #[serde(default)]
154 pub redirects: std::collections::HashMap<String, String>,
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn session_heartbeat_and_stale() {
163 let mut session = Session::new(
164 Uuid::new_v4(),
165 "test-fox".into(),
166 "http://localhost:3000".into(),
167 "Test".into(),
168 "Mozilla/5.0".into(),
169 "http://localhost:3000".into(),
170 );
171
172 assert!(!session.is_stale());
173 session.touch();
174 assert!(!session.is_stale());
175 }
176
177 #[test]
178 fn console_buffer_limit() {
179 let mut session = Session::new(
180 Uuid::new_v4(),
181 "test-owl".into(),
182 "http://localhost:3000".into(),
183 "Test".into(),
184 "Mozilla/5.0".into(),
185 "http://localhost:3000".into(),
186 );
187
188 let events: Vec<ConsoleEvent> = (0..600)
189 .map(|i| ConsoleEvent {
190 id: format!("evt-{i}"),
191 timestamp: i as i64,
192 level: crate::protocol::ConsoleLevel::Log,
193 args: vec![serde_json::json!(format!("message {i}"))],
194 })
195 .collect();
196
197 session.push_console_events(events);
198 assert_eq!(session.console_buffer.len(), MAX_CONSOLE_BUFFER);
199 }
200}