Skip to main content

codetether_browser/browser/offline/
replay.rs

1//! Replay captured HTTP responses through tetherscript.
2
3use 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}