Skip to content
Merged
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
61 changes: 60 additions & 1 deletion src/apps/desktop/src/api/browser_control_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,52 @@ fn default_cdp_port() -> u16 {
DEFAULT_CDP_PORT
}

/// Reattach to a browser that is already running with remote debugging on.
///
/// The browser remembers the remote debugging preference across its own
/// restarts, and it keeps an approved connection grant for as long as it stays
/// running — but BitFun's connection registry lives in this process, so every
/// BitFun restart otherwise leaves Settings reporting "not connected" until
/// something asks for the browser. Reattaching here restores that connection
/// without the user having to click anything.
///
/// This never starts a browser and never opens a settings page: when there is
/// no live endpoint to reattach to, it does nothing and leaves the on-demand
/// path to handle it.
pub fn init_on_startup() {
tokio::spawn(async {
let Ok(kind) = selected_browser_kind().await else {
return;
};
let Some(endpoint) = BrowserLauncher::user_profile_debug_endpoint(&kind) else {
return;
};
if CdpClient::browser_connection_for_kind(DEFAULT_CDP_PORT, &kind)
.await
.is_some()
{
return;
}
// A denial or an approval timeout is an ordinary outcome here, not an
// error worth surfacing: the user never asked for this connection.
match CdpClient::connect_user_profile_browser(
DEFAULT_CDP_PORT,
endpoint.port,
&kind,
&endpoint.web_socket_url,
)
.await
{
Ok(_) => log::info!("Reattached to the running {} profile on startup", kind),
Err(error) => log::info!(
"Could not reattach to the running {} profile on startup: {}",
kind,
error
),
}
});
}

async fn selected_browser_kind() -> Result<BrowserKind, String> {
let config = get_global_config_service()
.await
Expand Down Expand Up @@ -93,6 +139,10 @@ pub struct BrowserControlStatusResponse {
pub cdp_available: bool,
pub default_cdp_supported: bool,
pub default_cdp_enabled: bool,
/// The selected browser is running with remote debugging on, so BitFun can
/// attach whenever it needs to. Distinguishes "ready, nothing attached yet"
/// from "nothing to attach to", which both used to read as "not connected".
pub browser_ready: bool,
pub browser_kind: String,
pub browser_version: Option<String>,
pub port: u16,
Expand All @@ -107,7 +157,14 @@ pub async fn browser_control_get_status(
let port = request.port;
let configured_kind = selected_browser_kind().await?;
let default_cdp_supported = BrowserLauncher::supports_default_cdp(&configured_kind);
let default_cdp_enabled = BrowserLauncher::is_default_cdp_enabled(&configured_kind);
// Probe the live endpoint once and answer both questions from it: whether
// the persistent setting is on, and whether there is something to attach to
// right now. The probe is a file read plus a short local TCP connect, so it
// never prompts the browser the way attaching does.
let user_profile_endpoint = BrowserLauncher::user_profile_debug_endpoint(&configured_kind);
let default_cdp_enabled = default_cdp_supported
&& (user_profile_endpoint.is_some()
|| BrowserLauncher::is_default_cdp_enabled(&configured_kind));
let user_profile_connection =
CdpClient::browser_connection_for_kind(port, &configured_kind).await;
let legacy_version =
Expand All @@ -132,6 +189,7 @@ pub async fn browser_control_get_status(
}
});
let available = user_profile_connection.is_some() || legacy_matches_selection;
let browser_ready = available || user_profile_endpoint.is_some();

let (version, page_count, actual_kind) = if available {
let ver_info = if let Some(connection) = &user_profile_connection {
Expand Down Expand Up @@ -167,6 +225,7 @@ pub async fn browser_control_get_status(
cdp_available: available,
default_cdp_supported,
default_cdp_enabled,
browser_ready,
browser_kind: actual_kind.to_string(),
browser_version: version,
port,
Expand Down
10 changes: 10 additions & 0 deletions src/apps/desktop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,16 @@ pub async fn run() {
step_started,
);

// Reattach to a browser that is already running with remote
// debugging on, so a BitFun restart does not drop the connection.
let step_started = Instant::now();
api::browser_control_api::init_on_startup();
startup_trace.record_elapsed_step(
"native_setup",
"browser_control_init_on_startup",
step_started,
);

{
let step_started = Instant::now();
let _terminal_state: tauri::State<'_, api::terminal_api::TerminalState> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ impl BrowserLauncher {
provider::BrowserLauncher::is_default_cdp_enabled(kind)
}

/// Browser-level endpoint published by a browser that is running right now
/// with remote debugging enabled. `None` means there is nothing to attach
/// to without going through the launch flow.
pub fn user_profile_debug_endpoint(kind: &BrowserKind) -> Option<BrowserDebugEndpoint> {
provider::BrowserLauncher::user_profile_debug_endpoint(kind)
}

pub async fn launch_with_cdp(kind: &BrowserKind, port: u16) -> BitFunResult<LaunchResult> {
Ok(provider::BrowserLauncher::launch_with_cdp_options(
kind,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ const SessionSettingsPanels: React.FC<SessionSettingsPanelsProps> = ({ variant }

// ── Browser control state ───────────────────────────────────────────────
const [browserCdpAvailable, setBrowserCdpAvailable] = useState(false);
const [browserReady, setBrowserReady] = useState(false);
const [browserDefaultCdpSupported, setBrowserDefaultCdpSupported] = useState(false);
const [browserDefaultCdpEnabled, setBrowserDefaultCdpEnabled] = useState(false);
const [browserKind, setBrowserKind] = useState('');
Expand Down Expand Up @@ -191,6 +192,7 @@ const SessionSettingsPanels: React.FC<SessionSettingsPanelsProps> = ({ variant }
cdpAvailable: boolean;
defaultCdpSupported: boolean;
defaultCdpEnabled: boolean;
browserReady: boolean;
browserKind: string;
browserVersion: string | null;
port: number;
Expand All @@ -201,6 +203,7 @@ const SessionSettingsPanels: React.FC<SessionSettingsPanelsProps> = ({ variant }
setBrowserCdpAvailable(s.cdpAvailable);
setBrowserDefaultCdpSupported(s.defaultCdpSupported);
setBrowserDefaultCdpEnabled(s.defaultCdpEnabled);
setBrowserReady(s.browserReady);
setBrowserKind(s.browserKind);
setBrowserVersion(s.browserVersion);
setBrowserPageCount(s.pageCount);
Expand Down Expand Up @@ -899,9 +902,15 @@ const SessionSettingsPanels: React.FC<SessionSettingsPanelsProps> = ({ variant }
const computerUseScreenLabel = computerUseStatusLoading
? t('loading.text')
: computerUseScreen ? t('computerUse.granted') : t('computerUse.notGranted');
// A ready browser is not a failure state: BitFun attaches to it the moment
// something needs it, so say that rather than the bare "not connected".
const browserStatusLabel = browserCdpAvailable
? `${browserKind} · ${browserPageCount} ${t('browserControl.tabs')}`
: browserStatusLoading ? t('loading.text') : t('browserControl.notConnected');
: browserStatusLoading
? t('loading.text')
: browserReady
? t('browserControl.readyNotConnected')
: t('browserControl.notConnected');
const browserSelectOptions: SelectOption[] = browserOptions.map((option) => ({
value: option.value,
label: option.installed ? option.label : `${option.label} (${t('browserControl.notInstalled')})`,
Expand Down
1 change: 1 addition & 0 deletions src/web-ui/src/locales/en-US/settings/session-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@
"status": "Connection",
"statusDesc": "",
"notConnected": "Not connected",
"readyNotConnected": "Ready, connects on use",
"refreshStatus": "Refresh status",
"connect": "Connect",
"defaultCdp": "Default CDP",
Expand Down
1 change: 1 addition & 0 deletions src/web-ui/src/locales/zh-CN/settings/session-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@
"status": "连接状态",
"statusDesc": "",
"notConnected": "未连接",
"readyNotConnected": "已就绪,使用时自动连接",
"refreshStatus": "刷新状态",
"connect": "连接浏览器",
"defaultCdp": "默认 CDP",
Expand Down
1 change: 1 addition & 0 deletions src/web-ui/src/locales/zh-TW/settings/session-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
"status": "連接狀態",
"statusDesc": "",
"notConnected": "未連接",
"readyNotConnected": "已就緒,使用時自動連接",
"refreshStatus": "重新整理狀態",
"connect": "連接瀏覽器",
"defaultCdp": "預設 CDP",
Expand Down
Loading