use anyhow::{Context, Result};
use par_term_emu_core_rust::terminal::RecordingEventType;
use super::core::SessionLogger;
use crate::config::SessionLogFormat;
impl SessionLogger {
pub(super) fn write_plain_redaction_warning(&mut self) -> Result<()> {
use std::io::Write;
let warning = "\
# par-term session log
# WARNING: Credential redaction is heuristic and has known limitations.
# Patterns matched: common KEY=value exports, password/passphrase prompts,
# Bearer tokens, PEM private key blocks, CI tokens (GITHUB_TOKEN, HEROKU_API_KEY, etc.)
# Known gaps: secrets printed without a recognisable variable name, base64-encoded
# tokens, secrets embedded in JSON/YAML output, or any novel credential format.
# RECOMMENDATION: Disable session logging (Settings > Logging) before working with
# credentials, API keys, or other sensitive values in the terminal.
\n";
if let Some(ref mut writer) = self.writer {
writer.write_all(warning.as_bytes()).with_context(|| {
format!(
"Failed to write redaction warning to {:?}",
self.output_path
)
})?;
}
Ok(())
}
pub(super) fn write_html_header(&mut self) -> Result<()> {
use std::io::Write;
let header = format!(
r#"<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{}</title>
<style>
body {{
background-color: #1e1e1e;
color: #d4d4d4;
font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
font-size: 14px;
padding: 20px;
white-space: pre-wrap;
word-wrap: break-word;
}}
.timestamp {{
color: #808080;
font-size: 10px;
}}
</style>
</head>
<body>
<pre>
"#,
self.title.as_deref().unwrap_or("Terminal Session")
);
if let Some(ref mut writer) = self.writer {
writer.write_all(header.as_bytes()).with_context(|| {
format!("Failed to write HTML header to {:?}", self.output_path)
})?;
}
Ok(())
}
pub(super) fn write_html_footer(&mut self) -> Result<()> {
use std::io::Write;
let footer = r#"
</pre>
</body>
</html>
"#;
if let Some(ref mut writer) = self.writer {
writer.write_all(footer.as_bytes()).with_context(|| {
format!("Failed to write HTML footer to {:?}", self.output_path)
})?;
}
Ok(())
}
pub(super) fn write_asciicast(&mut self) -> Result<()> {
use std::io::Write;
if let Some(ref recording) = self.recording {
let header = serde_json::json!({
"version": 2,
"width": recording.initial_size.0,
"height": recording.initial_size.1,
"timestamp": recording.created_at / 1000, "title": &recording.title,
"env": recording.env,
});
if let Some(ref mut writer) = self.writer {
writeln!(writer, "{}", header).with_context(|| {
format!("Failed to write asciicast header to {:?}", self.output_path)
})?;
for event in &recording.events {
let time_seconds = event.timestamp as f64 / 1000.0;
match event.event_type {
RecordingEventType::Output => {
let data_str = String::from_utf8_lossy(&event.data);
let line = serde_json::json!([time_seconds, "o", data_str]);
writeln!(writer, "{}", line)?;
}
RecordingEventType::Input => {
let data_str = String::from_utf8_lossy(&event.data);
let line = serde_json::json!([time_seconds, "i", data_str]);
writeln!(writer, "{}", line)?;
}
RecordingEventType::Resize => {
if let Some((cols, rows)) = event.metadata {
let line = serde_json::json!([
time_seconds,
"r",
format!("{}x{}", cols, rows)
]);
writeln!(writer, "{}", line)?;
}
}
RecordingEventType::Marker => {
let label = String::from_utf8_lossy(&event.data);
let line = serde_json::json!([time_seconds, "m", label]);
writeln!(writer, "{}", line)?;
}
RecordingEventType::Metadata => {
let data_str = String::from_utf8_lossy(&event.data);
let line = serde_json::json!([time_seconds, "m", data_str]);
writeln!(writer, "{}", line)?;
}
}
}
}
}
Ok(())
}
}
pub(super) fn finalize_format(logger: &mut SessionLogger) -> Result<()> {
match logger.format {
SessionLogFormat::Plain => Ok(()),
SessionLogFormat::Html => logger.write_html_footer(),
SessionLogFormat::Asciicast => logger.write_asciicast(),
}
}