ephem_debugger_rs/
browser.rs1use std::sync::Arc;
24
25use crate::protocol::LogEntry;
26use crate::store::LogStore;
27
28pub const CLIENT_SCRIPT: &str = include_str!("client.js");
30
31pub const SCRIPT_TAGS: &str = r#"<script>window.__DEBUGGER_INGEST_URL__="/_/d";</script><script src="/_/d.js" defer></script>"#;
36
37pub fn inject_scripts(html: &str) -> String {
43 if html.contains("__DEBUGGER_INGEST_URL__") {
44 return html.to_string();
45 }
46 if let Some(idx) = html.rfind("</body>") {
47 let mut result = String::with_capacity(html.len() + SCRIPT_TAGS.len());
48 result.push_str(&html[..idx]);
49 result.push_str(SCRIPT_TAGS);
50 result.push_str(&html[idx..]);
51 result
52 } else {
53 html.to_string()
54 }
55}
56
57pub fn ingest_entries(store: &Arc<LogStore>, entries: &[serde_json::Value]) {
63 for value in entries {
64 match serde_json::from_value::<LogEntry>(value.clone()) {
65 Ok(entry) => store.push(entry),
66 Err(_) => {
67 }
71 }
72 }
73}
74
75pub const CORS_HEADERS: [(&str, &str); 4] = [
77 ("access-control-allow-origin", "*"),
78 ("access-control-allow-methods", "POST, OPTIONS"),
79 ("access-control-allow-headers", "content-type"),
80 ("access-control-allow-credentials", "true"),
81];
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn inject_into_html() {
89 let html = "<html><body><h1>Hello</h1></body></html>";
90 let result = inject_scripts(html);
91 assert!(result.contains(SCRIPT_TAGS));
92 assert!(result.contains("</body>"));
93 let script_pos = result.find(SCRIPT_TAGS).unwrap();
95 let body_pos = result.rfind("</body>").unwrap();
96 assert!(script_pos < body_pos);
97 }
98
99 #[test]
100 fn inject_idempotent() {
101 let html = "<html><body><h1>Hello</h1></body></html>";
102 let first = inject_scripts(html);
103 let second = inject_scripts(&first);
104 assert_eq!(first, second);
105 }
106
107 #[test]
108 fn inject_no_body_tag() {
109 let html = "<html><h1>Hello</h1></html>";
110 let result = inject_scripts(html);
111 assert_eq!(result, html);
112 }
113
114 #[test]
115 fn client_script_is_not_empty() {
116 assert!(!CLIENT_SCRIPT.is_empty());
117 assert!(CLIENT_SCRIPT.contains("__DEBUGGER_INITIALIZED__"));
118 }
119
120 #[test]
121 fn ingest_valid_console_entry() {
122 let session = crate::protocol::create_session("test", 3000);
123 let store = Arc::new(LogStore::new(session));
124
125 let entries = vec![serde_json::json!({
126 "type": "console",
127 "level": "info",
128 "args": ["hello from browser"],
129 "timestamp": 1700000000000_i64,
130 "source": "browser"
131 })];
132
133 ingest_entries(&store, &entries);
134
135 let resp = store.query("console", &crate::protocol::Filters::default());
136 assert_eq!(resp.data.len(), 1);
137 }
138
139 #[test]
140 fn ingest_invalid_entry_is_dropped() {
141 let session = crate::protocol::create_session("test", 3000);
142 let store = Arc::new(LogStore::new(session));
143
144 let entries = vec![serde_json::json!({
145 "garbage": true
146 })];
147
148 ingest_entries(&store, &entries);
149
150 let resp = store.query("all", &crate::protocol::Filters::default());
151 assert_eq!(resp.data.len(), 0);
152 }
153}