Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ steel browser start [options]
- -s, **--session**: Named session key for create-or-attach behavior
- **--stealth**: Apply stealth preset on new sessions (humanized interactions + auto CAPTCHA solving / solveCaptcha=true)
- -p, **--proxy**: Proxy URL for new sessions (for example, http://user:pass@host:port)
- **--session-timeout**: Session timeout in milliseconds (create-time only)
- **--session-timeout**: Session timeout in milliseconds (create-time only; defaults to `browser.sessionTimeoutMs` in `~/.config/steel/config.json`)
- **--inactivity-timeout**: Inactivity timeout in milliseconds — release the session when no CDP command or remote input is received for this long (create-time only; defaults to 120000 / 2 minutes, pass 0 to disable)
- **--session-headless**: Create new sessions in headless mode (create-time only)
- **--session-region**: Preferred session region (create-time only)
Expand Down
38 changes: 34 additions & 4 deletions src/commands/browser/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::browser::daemon::protocol::{
};
use crate::browser::lifecycle::sanitize_connect_url;
use crate::browser::profile_store;
use crate::config::settings::read_config;
use crate::status;
use crate::util::{api, output};

Expand All @@ -21,7 +22,7 @@ pub struct Args {
#[arg(short = 'p', long)]
pub proxy: Option<String>,

/// Session timeout in milliseconds (create-time only)
/// Session timeout in milliseconds (create-time only; defaults to browser.sessionTimeoutMs in config.json)
#[arg(long = "session-timeout")]
pub session_timeout: Option<u64>,

Expand Down Expand Up @@ -60,6 +61,10 @@ pub struct Args {

pub async fn run(args: Args, session: Option<&str>) -> anyhow::Result<()> {
let (mode, base_url, auth) = api::resolve_with_auth();
let configured_session_timeout = read_config()
.ok()
.and_then(|config| config.browser_session_timeout_ms());
let session_timeout = resolve_session_timeout(args.session_timeout, configured_session_timeout);

let session_name = session.unwrap_or("default").to_string();

Expand All @@ -82,11 +87,11 @@ pub async fn run(args: Args, session: Option<&str>) -> anyhow::Result<()> {
let proxy_enabled = args.proxy.is_some();
let namespace_set = args.namespace.is_some();
let inactivity_timeout_ms = resolve_inactivity_timeout(args.inactivity_timeout);
if let (Some(timeout), Some(inactivity)) = (args.session_timeout, inactivity_timeout_ms)
if let (Some(timeout), Some(inactivity)) = (session_timeout, inactivity_timeout_ms)
&& inactivity >= timeout
{
eprintln!(
"warning: --inactivity-timeout ({inactivity}ms) >= --session-timeout ({timeout}ms); inactivity timeout has no effect because the session timeout elapses first"
"warning: --inactivity-timeout ({inactivity}ms) >= session timeout ({timeout}ms); inactivity timeout has no effect because the session timeout elapses first"
);
}

Expand All @@ -109,7 +114,7 @@ pub async fn run(args: Args, session: Option<&str>) -> anyhow::Result<()> {
session_name: session_name.clone(),
stealth: args.stealth,
proxy_url: args.proxy,
timeout_ms: args.session_timeout,
timeout_ms: session_timeout,
inactivity_timeout_ms,
headless: args.session_headless,
region: args.session_region,
Expand Down Expand Up @@ -235,6 +240,13 @@ fn humanize_secs(secs: u64) -> String {
}
}

const fn resolve_session_timeout(explicit: Option<u64>, configured: Option<u64>) -> Option<u64> {
match explicit {
Some(timeout) => Some(timeout),
None => configured,
}
}

const fn resolve_inactivity_timeout(explicit: Option<u64>) -> Option<u64> {
match explicit {
None => Some(DEFAULT_INACTIVITY_TIMEOUT_MS),
Expand All @@ -247,6 +259,24 @@ const fn resolve_inactivity_timeout(explicit: Option<u64>) -> Option<u64> {
mod tests {
use super::*;

#[test]
fn session_timeout_uses_config_when_flag_is_unset() {
assert_eq!(resolve_session_timeout(None, Some(900000)), Some(900000));
}

#[test]
fn session_timeout_flag_overrides_config() {
assert_eq!(
resolve_session_timeout(Some(300000), Some(900000)),
Some(300000)
);
}

#[test]
fn session_timeout_explicit_zero_overrides_config() {
assert_eq!(resolve_session_timeout(Some(0), Some(900000)), Some(0));
}

#[test]
fn inactivity_default_when_unset() {
assert_eq!(
Expand Down
8 changes: 6 additions & 2 deletions src/commands/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,13 @@ pub async fn run(_args: Args) -> anyhow::Result<()> {

if let Some(ref cfg) = config
&& let Some(ref browser) = cfg.browser
&& let Some(ref url) = browser.api_url
{
println!("browser.apiUrl: {url}");
if let Some(ref url) = browser.api_url {
println!("browser.apiUrl: {url}");
}
if let Some(timeout_ms) = cfg.browser_session_timeout_ms() {
println!("browser.sessionTimeoutMs: {timeout_ms}");
}
}

if let Some(ref cfg) = config {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Browser Sessions:
steel browser start [--session <name>] Create or attach to a browser session
--stealth Enable stealth mode (humanize + auto CAPTCHA)
-p, --proxy <proxy> Use residential proxy
--session-timeout <ms> Session timeout
--session-timeout <ms> Session timeout (defaults to browser.sessionTimeoutMs in config.json)
--session-solve-captcha Enable manual CAPTCHA solving
--profile <name> Named profile to persist browser state
--update-profile Save state back to profile on session end
Expand Down
17 changes: 16 additions & 1 deletion src/config/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ pub struct Config {
pub struct BrowserConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub api_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_timeout_ms: Option<u64>,
}

#[derive(Debug, Serialize, Deserialize, Default, Clone)]
Expand All @@ -137,6 +139,14 @@ impl Config {
.filter(|s| !s.trim().is_empty())
}

/// Extract the default browser session timeout from config.browser.sessionTimeoutMs.
pub fn browser_session_timeout_ms(&self) -> Option<u64> {
self.browser
.as_ref()
.and_then(|b| b.session_timeout_ms)
.filter(|timeout| *timeout > 0)
}

pub fn telemetry_disabled(&self) -> bool {
self.telemetry
.as_ref()
Expand Down Expand Up @@ -196,6 +206,7 @@ mod tests {
instance: Some("cloud".into()),
browser: Some(BrowserConfig {
api_url: Some("http://localhost:4000/v1".into()),
session_timeout_ms: Some(3_600_000),
}),
telemetry: Some(TelemetryConfig {
disabled: Some(true),
Expand All @@ -209,6 +220,7 @@ mod tests {
assert_eq!(loaded.name.as_deref(), Some("CLI"));
assert_eq!(loaded.instance.as_deref(), Some("cloud"));
assert_eq!(loaded.local_api_url(), Some("http://localhost:4000/v1"));
assert_eq!(loaded.browser_session_timeout_ms(), Some(3_600_000));
assert!(loaded.telemetry_disabled());
}

Expand All @@ -229,13 +241,14 @@ mod tests {
let path = dir.path().join("config.json");
std::fs::write(
&path,
r#"{"apiKey":"k","name":"n","instance":"cloud","browser":{"apiUrl":"http://x"},"telemetry":{"disabled":true}}"#,
r#"{"apiKey":"k","name":"n","instance":"cloud","browser":{"apiUrl":"http://x","sessionTimeoutMs":900000},"telemetry":{"disabled":true}}"#,
)
.unwrap();

let config = read_config_from(&path).unwrap();
assert_eq!(config.api_key.as_deref(), Some("k"));
assert_eq!(config.local_api_url(), Some("http://x"));
assert_eq!(config.browser_session_timeout_ms(), Some(900000));
assert!(config.telemetry_disabled());
}

Expand All @@ -257,10 +270,12 @@ mod tests {
let config = Config {
browser: Some(BrowserConfig {
api_url: Some(" ".into()),
session_timeout_ms: Some(0),
}),
..Default::default()
};
assert_eq!(config.local_api_url(), None);
assert_eq!(config.browser_session_timeout_ms(), None);
}

#[test]
Expand Down