@@ -1,4 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -90,10 +91,25 @@ impl CdpClient {
|
||||
Ok(text) => text,
|
||||
Err(_) => continue,
|
||||
},
|
||||
Ok(Message::Close(_)) => break,
|
||||
Ok(Message::Close(frame)) => {
|
||||
if std::env::var("AGENT_BROWSER_DEBUG").is_ok() {
|
||||
let reason = frame
|
||||
.as_ref()
|
||||
.map(|f| format!("code={}, reason={}", f.code, f.reason))
|
||||
.unwrap_or_else(|| "no frame".to_string());
|
||||
let _ =
|
||||
writeln!(std::io::stderr(), "[cdp] WebSocket Close: {}", reason);
|
||||
}
|
||||
break;
|
||||
}
|
||||
Ok(Message::Pong(_)) => continue,
|
||||
Ok(_) => continue,
|
||||
Err(_) => break,
|
||||
Err(e) => {
|
||||
if std::env::var("AGENT_BROWSER_DEBUG").is_ok() {
|
||||
let _ = writeln!(std::io::stderr(), "[cdp] WebSocket Error: {}", e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Broadcast raw message for inspect proxy subscribers before typed parse,
|
||||
|
||||
@@ -22,6 +22,27 @@ pub async fn run_daemon(session: &str) {
|
||||
let _ = fs::create_dir_all(&socket_dir);
|
||||
}
|
||||
|
||||
// When debug mode is on, redirect stderr to a log file so daemon
|
||||
// output can be inspected (the daemon normally has stderr piped to its
|
||||
// parent which drops the read end after startup).
|
||||
#[cfg(unix)]
|
||||
if env::var("AGENT_BROWSER_DEBUG").is_ok() {
|
||||
let log_path = socket_dir.join(format!("{}.log", session));
|
||||
if let Ok(file) = fs::File::create(&log_path) {
|
||||
use std::os::unix::io::IntoRawFd;
|
||||
let fd = file.into_raw_fd();
|
||||
unsafe {
|
||||
libc::dup2(fd, 2);
|
||||
libc::close(fd);
|
||||
}
|
||||
let _ = writeln!(
|
||||
std::io::stderr(),
|
||||
"[daemon] Debug logging started for session: {}",
|
||||
session
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let pid_path = socket_dir.join(format!("{}.pid", session));
|
||||
let _ = fs::write(&pid_path, process::id().to_string());
|
||||
|
||||
|
||||
@@ -776,6 +776,10 @@ async fn cdp_event_loop(
|
||||
let vw = *viewport_width.lock().await;
|
||||
let vh = *viewport_height.lock().await;
|
||||
|
||||
let eng = last_engine.read().await.clone();
|
||||
let supports_screencast = eng == "chrome";
|
||||
|
||||
if supports_screencast {
|
||||
let _ = client_arc
|
||||
.send_command(
|
||||
"Page.startScreencast",
|
||||
@@ -789,19 +793,19 @@ async fn cdp_event_loop(
|
||||
session_id.as_deref(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
{
|
||||
let mut sc = screencasting.lock().await;
|
||||
*sc = true;
|
||||
*sc = supports_screencast;
|
||||
}
|
||||
|
||||
// Broadcast screencasting:true status with current viewport
|
||||
let eng = last_engine.read().await.clone();
|
||||
// Broadcast connection status with current viewport
|
||||
let rec = *recording.lock().await;
|
||||
let status = json!({
|
||||
"type": "status",
|
||||
"connected": true,
|
||||
"screencasting": true,
|
||||
"screencasting": supports_screencast,
|
||||
"viewportWidth": vw,
|
||||
"viewportHeight": vh,
|
||||
"engine": eng,
|
||||
@@ -814,10 +818,12 @@ async fn cdp_event_loop(
|
||||
tokio::select! {
|
||||
changed = shutdown_rx.changed() => {
|
||||
if changed.is_err() || *shutdown_rx.borrow() {
|
||||
if supports_screencast {
|
||||
let session_id = cdp_session_id.read().await.clone();
|
||||
let _ = client_arc
|
||||
.send_command_no_params("Page.stopScreencast", session_id.as_deref())
|
||||
.await;
|
||||
}
|
||||
let mut sc = screencasting.lock().await;
|
||||
*sc = false;
|
||||
return;
|
||||
@@ -943,9 +949,11 @@ async fn cdp_event_loop(
|
||||
let count = *client_count.lock().await;
|
||||
let new_session_id = cdp_session_id.read().await.clone();
|
||||
if count == 0 {
|
||||
if supports_screencast {
|
||||
let _ = client_arc
|
||||
.send_command_no_params("Page.stopScreencast", session_id.as_deref())
|
||||
.await;
|
||||
}
|
||||
let mut sc = screencasting.lock().await;
|
||||
*sc = false;
|
||||
break;
|
||||
@@ -962,10 +970,11 @@ async fn cdp_event_loop(
|
||||
let new_vh = *viewport_height.lock().await;
|
||||
let viewport_changed = new_vw != vw || new_vh != vh;
|
||||
if client_changed || session_changed || viewport_changed {
|
||||
// Stop screencast, restart loop to pick up new settings
|
||||
if supports_screencast {
|
||||
let _ = client_arc
|
||||
.send_command_no_params("Page.stopScreencast", session_id.as_deref())
|
||||
.await;
|
||||
}
|
||||
let mut sc = screencasting.lock().await;
|
||||
*sc = false;
|
||||
client_notify.notify_one();
|
||||
|
||||
@@ -194,14 +194,20 @@ export function Viewport() {
|
||||
addressRef.current?.blur();
|
||||
|
||||
const target = normalizeUrl(addressValue);
|
||||
const previousUrl = url;
|
||||
setAddressValue(target);
|
||||
setNavigating(true);
|
||||
try {
|
||||
await runCmd("navigate", target);
|
||||
const result = await runCmd("navigate", target);
|
||||
if (!result.success) {
|
||||
setAddressValue(previousUrl || "about:blank");
|
||||
}
|
||||
} catch {
|
||||
setAddressValue(previousUrl || "about:blank");
|
||||
} finally {
|
||||
setNavigating(false);
|
||||
}
|
||||
}, [addressValue, navigating, runCmd]);
|
||||
}, [addressValue, navigating, runCmd, url]);
|
||||
|
||||
const drawFrame = useCallback((base64: string) => {
|
||||
const canvas = canvasRef.current;
|
||||
|
||||
@@ -8,12 +8,21 @@ export interface ExecResult {
|
||||
}
|
||||
|
||||
export async function execCommand(args: string[]): Promise<ExecResult> {
|
||||
try {
|
||||
const resp = await fetch(`http://localhost:${DASHBOARD_PORT}/api/exec`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ args }),
|
||||
});
|
||||
return resp.json();
|
||||
} catch {
|
||||
return {
|
||||
success: false,
|
||||
exit_code: null,
|
||||
stdout: "",
|
||||
stderr: "Network error: dashboard server unreachable",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function sessionArgs(session: string, ...args: string[]): string[] {
|
||||
@@ -21,10 +30,14 @@ export function sessionArgs(session: string, ...args: string[]): string[] {
|
||||
}
|
||||
|
||||
export async function killSession(session: string): Promise<{ success: boolean; killed_pid?: number }> {
|
||||
try {
|
||||
const resp = await fetch(`http://localhost:${DASHBOARD_PORT}/api/kill`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ session }),
|
||||
});
|
||||
return resp.json();
|
||||
} catch {
|
||||
return { success: false };
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user