ws-bridge 0.2.1

Strongly-typed WebSocket endpoints for Rust — define once, use everywhere (axum server, yew/gloo browser client, tokio-tungstenite native client)
Documentation
# Reconnection (`reconnect` feature)

The `reconnect` feature provides `ReconnectingWs`, a wrapper that automatically reconnects with exponential backoff when the WebSocket connection drops.

## Setup

```toml
[dependencies]
ws-bridge = { version = "0.1", features = ["reconnect", "native-client"] }
tokio = { version = "1", features = ["full"] }
```

## Quick start

For native clients, use the convenience constructor:

```rust
use ws_bridge::reconnect::{self, BackoffConfig};

let mut ws = reconnect::connect_native::<EchoEndpoint>(
    "ws://localhost:3000".into(),
    BackoffConfig::default(),
);

// recv() automatically reconnects if the connection drops
while let Some(result) = ws.recv().await {
    match result {
        Ok(msg) => println!("{msg:?}"),
        Err(e) => eprintln!("decode error: {e}"),
    }
}
```

## Backoff configuration

```rust
use std::time::Duration;
use ws_bridge::reconnect::BackoffConfig;

let config = BackoffConfig {
    initial: Duration::from_millis(500),  // First retry after 500ms
    max: Duration::from_secs(30),         // Cap at 30s between retries
    multiplier: 2.0,                      // Double the delay each attempt
};
```

The default config is:
- `initial`: 1 second
- `max`: 30 seconds
- `multiplier`: 2.0

With the default config, retry delays are: 1s, 2s, 4s, 8s, 16s, 30s, 30s, 30s, ...

## Custom connect functions

`ReconnectingWs::new()` accepts any async function that returns `Option<WsConnection<S, R>>`:

```rust
use ws_bridge::reconnect::{ReconnectingWs, BackoffConfig};

let mut ws = ReconnectingWs::new(
    BackoffConfig::default(),
    || async {
        // Custom connection logic — return Some(conn) on success, None on failure
        ws_bridge::native_client::connect::<MyEndpoint>("ws://localhost:3000")
            .await
            .ok()
    },
);
```

This is useful when you need custom connection setup (e.g., adding auth headers, using a specific TLS config).

## Behavior

### Sending

`send()` attempts to send the message. If the connection is down, it reconnects first, then sends. If the send fails, it drops the connection and retries on the next loop iteration.

```rust
ws.send(ClientMsg::Ping).await?;
```

The `S: Clone` bound on the send type is required because the message may need to be resent after a reconnection.

### Receiving

`recv()` returns the next message. If the connection drops (`None` or `RecvError::Closed`), it transparently reconnects and continues receiving. Decode errors (`RecvError::Decode`) are passed through to the caller.

```rust
while let Some(result) = ws.recv().await {
    match result {
        Ok(msg) => { /* handle message */ }
        Err(ws_bridge::RecvError::Decode(e)) => {
            // Bad message from server — connection is still alive
            eprintln!("decode error: {e}");
        }
        // RecvError::Closed is handled internally (triggers reconnect)
        _ => {}
    }
}
// recv() returns None only if reconnection permanently fails
// (currently retries indefinitely, so this loop runs forever)
```

### Forced reconnection

Call `reconnect()` to explicitly drop and re-establish the connection:

```rust
ws.reconnect().await;
```

## Retry behavior

- On successful connect or message exchange, the attempt counter resets to 0.
- On connection failure, the delay increases exponentially up to `max`.
- Reconnection retries indefinitely — there is no max attempt limit.
- `recv()` never returns `None` (it always reconnects), so loops using `while let Some(...)` run forever.

## Example: resilient client

```rust
use ws_bridge::reconnect::{self, BackoffConfig};
use std::time::Duration;

#[tokio::main]
async fn main() {
    let config = BackoffConfig {
        initial: Duration::from_secs(1),
        max: Duration::from_secs(30),
        multiplier: 2.0,
    };

    let mut ws = reconnect::connect_native::<MyEndpoint>(
        "ws://localhost:3000".into(),
        config,
    );

    loop {
        // Send a message (reconnects if needed)
        if let Err(e) = ws.send(ClientMsg::Heartbeat).await {
            eprintln!("send failed: {e}");
            continue;
        }

        // Wait for response
        if let Some(Ok(msg)) = ws.recv().await {
            println!("received: {msg:?}");
        }
    }
}
```