# Connection Architecture Research

This document outlines the current WebSocket connection architecture and message flow in `browser-hybrid`.

## Overview
`browser-hybrid` uses a hierarchical connection management system based on the Chrome DevTools Protocol (CDP).

### Class Diagram
```mermaid
classDiagram
    class Browser {
        +ConnectionPool _pool
        +Tab current_tab
        +_send_cdp(Connection, method, params)
        +_get_connection(Tab)
    }
    class ConnectionPool {
        +dict[str, Connection] _connections
        +get_connection(tab_id, ws_url)
        +_evict_oldest()
        +close_all()
    }
    class Connection {
        +ClientConnection _ws
        +dict[int, Future] _pending
        +asyncio.Task _receive_task
        +send(method, params)
        +_receive_loop()
        +health_check()
    }
    Browser "1" *-- "1" ConnectionPool
    ConnectionPool "1" *-- "*" Connection
```

---

## Message Flow
The system uses a **Request-Response** pattern over persistent WebSockets.

1.  **Initiation**: `Browser` methods (like `navigate` or `click`) call `_get_connection()` to retrieve a `Connection` object via the `ConnectionPool`.
2.  **Sending**: 
    - `Browser._send_cdp()` calls `Connection.send(method, params)`.
    - `Connection.send()` assigns a unique monotonic `id` to the message.
    - It creates an `asyncio.Future` and stores it in `_pending[id]`.
    - It sends the JSON-encoded command to Chrome over the WebSocket.
3.  **Receiving**:
    - The `Connection._receive_loop()` (running in the background) sits in an `async for` loop over the WebSocket.
    - When a message arrives, it's parsed as JSON.
    - If the message contains an `id` matching an entry in `_pending`, the payload is set as the result of the `Future`.
4.  **Completion**:
    - `Connection.send()` awaits the `Future` (with timeout logic).
    - Once the response is received, the `Future` resolves, and `send()` returns the result.

---

## Call Sites

### WebSocket `send` (Outgoing)
- **`src/browser_hybrid/connection.py`**: 
    - `Connection.send()` → `await self._ws.send(json.dumps(message))` (line 172)
    - Primary entry point for all CDP commands.

### WebSocket `recv` (Incoming)
- **`src/browser_hybrid/connection.py`**:
    - `Connection._receive_loop()` → `async for message in self._ws:` (line 199)
    - Continuously listens and dispatches to pending futures or processes events.

---

## Error Handling
The system uses a tiered approach for handling errors at different levels.

### 1. Connection-Level Errors
- **Connection Failure**: If `websockets.connect()` fails or times out in `Connection.connect()`, a `ConnectionError` or `TimeoutError` is raised.
- **WebSocket Closure**: If the socket closes during operation, the `_receive_loop` exits and clears all `_pending` futures with a `CancelledError` (or similar). Subsequent `send()` calls check `is_connected` and raise `ConnectionError`.

### 2. CDP-Level Errors
- CDP responses containing an `"error"` field are intercepted at two points:
    1.  **`Connection.send()`**: Uses `parse_cdp_error(result, method)` to detect and raise `CDPError`.
    2.  **`Browser._send_cdp()`**: Re-checks and raises `CDPError` for safety (redundant but robust).

### 3. Timeout Handling
- Every `send()` operation is wrapped in `asyncio.wait_for()` with a configurable `timeout`.
- If a response doesn't arrive in time, `src.browser_hybrid.errors.TimeoutError` is raised.

### 4. Waiters/Higher-level Logic
- Methods like `wait_for_load_state` catch internal errors from `_send_cdp` inside a polling loop to allow for temporary unavailability (e.g., during navigation where JS context might be detached).
