codetether_browser/browser/offline/
replay.rs1use anyhow::Result;
4use base64::Engine as _;
5use base64::engine::general_purpose::STANDARD;
6use std::path::Path;
7
8use super::record::Capture;
9
10#[derive(Debug, serde::Serialize)]
11pub struct ReplayReport {
12 pub url: String,
13 pub status: u16,
14 pub body_bytes: usize,
15 pub content_type: Option<String>,
16 pub eval_result: String,
17}
18
19pub fn run(capture: &Path) -> Result<String> {
20 let text = std::fs::read_to_string(capture)?;
21 let cap: Capture = serde_json::from_str(&text)?;
22 let body = STANDARD.decode(cap.body_base64.as_bytes())?;
23 let report = replay_capture(&cap, &body)?;
24 Ok(serde_json::to_string_pretty(&report)?)
25}
26
27#[cfg(feature = "tetherscript")]
28fn replay_capture(cap: &Capture, body: &[u8]) -> Result<ReplayReport> {
29 use tetherscript::browser_session::BrowserSession;
30 let html = String::from_utf8_lossy(body).to_string();
31 let mut session = BrowserSession::new();
32 session.goto_html(cap.url.clone(), html);
33 Ok(report(cap, body, eval_title(&mut session)))
34}
35
36#[cfg(feature = "tetherscript")]
37fn eval_title(session: &mut tetherscript::browser_session::BrowserSession) -> String {
38 session
39 .eval_js("document.title")
40 .map(|value| format!("{value:?}"))
41 .unwrap_or_else(|error| format!("eval-error: {error}"))
42}
43
44#[cfg(not(feature = "tetherscript"))]
45fn replay_capture(_: &Capture, _: &[u8]) -> Result<ReplayReport> {
46 anyhow::bail!("replay requires the `tetherscript` feature");
47}
48
49fn report(cap: &Capture, body: &[u8], eval_result: String) -> ReplayReport {
50 ReplayReport {
51 url: cap.url.clone(),
52 status: cap.status,
53 body_bytes: body.len(),
54 content_type: cap.content_type.clone(),
55 eval_result,
56 }
57}