fix(connect): relay answers Browser.getVersion locally (stops reconnect storm)
ROOT CAUSE of per-session command drift on the extension path: the daemon's liveness check (`is_connection_alive` → `Browser.getVersion`) is a BROWSER-level command. The relay only answered Target.* locally and forwarded the rest, so Browser.getVersion went to the extension, which can only do per-tab chrome.debugger → it errored → CdpClient saw TransportError → connection deemed DEAD → the daemon closed + reconnected + re-ran discover_and_attach_targets on EVERY command. Each re-discover rebuilds pages from the relay's minimal targetInfo and resets active_page_index=0, so eval/get-title/screenshot drifted to the first tab (about:blank / a foreign focused tab). Reproduced locally (throwaway Chrome + Extensions.loadUnpacked + fork.24 nm-host): trace showed discover_and_attach_targets running on every command (pages before=0) and [ev] active_idx reset to 0. Fix: relay answers Browser.getVersion locally with a stub version (like getTargets), so the liveness probe succeeds → connection stays alive → no reconnect/re-discover → the session's active tab is preserved. Pairs with fork.24's add_background_page. relay.rs: 10 unit tests.
This commit is contained in:
@@ -107,6 +107,22 @@ impl RelayState {
|
|||||||
let session_id = raw.get("sessionId").and_then(|s| s.as_str());
|
let session_id = raw.get("sessionId").and_then(|s| s.as_str());
|
||||||
|
|
||||||
match method {
|
match method {
|
||||||
|
// Browser-level command the daemon uses as its liveness probe
|
||||||
|
// (`is_connection_alive` → `Browser.getVersion`). The extension only
|
||||||
|
// speaks per-tab `chrome.debugger`, so forwarding it errors → the
|
||||||
|
// daemon would deem the connection dead and reconnect+re-discover on
|
||||||
|
// EVERY command, resetting the active tab (eval/screenshot drift).
|
||||||
|
// Answer it locally so the relay connection reads as alive.
|
||||||
|
"Browser.getVersion" => ClientRoute::Local(json!({
|
||||||
|
"id": id,
|
||||||
|
"result": {
|
||||||
|
"protocolVersion": "1.3",
|
||||||
|
"product": "Chrome/ab-connect-relay",
|
||||||
|
"revision": "",
|
||||||
|
"userAgent": "",
|
||||||
|
"jsVersion": ""
|
||||||
|
}
|
||||||
|
})),
|
||||||
// Discovery is best-effort and event-driven in real CDP; abs only
|
// Discovery is best-effort and event-driven in real CDP; abs only
|
||||||
// reads the getTargets result, so an empty ack is enough here.
|
// reads the getTargets result, so an empty ack is enough here.
|
||||||
"Target.setDiscoverTargets" | "Target.setAutoAttach" => {
|
"Target.setDiscoverTargets" | "Target.setAutoAttach" => {
|
||||||
@@ -302,6 +318,21 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn browser_get_version_is_answered_locally() {
|
||||||
|
// Liveness probe must NOT be forwarded (the extension can't do
|
||||||
|
// browser-level commands) — else the daemon reconnects on every command.
|
||||||
|
let mut s = RelayState::new();
|
||||||
|
let route = s.route_client_command(1, &json!({ "id": 7, "method": "Browser.getVersion" }));
|
||||||
|
match route {
|
||||||
|
ClientRoute::Local(v) => {
|
||||||
|
assert_eq!(v["id"], 7);
|
||||||
|
assert!(v["result"]["protocolVersion"].is_string());
|
||||||
|
}
|
||||||
|
_ => panic!("Browser.getVersion must be answered locally"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn attach_to_target_returns_known_session() {
|
fn attach_to_target_returns_known_session() {
|
||||||
let mut s = RelayState::new();
|
let mut s = RelayState::new();
|
||||||
|
|||||||
Reference in New Issue
Block a user