diff --git a/advanced/websocket-handling.mdx b/advanced/websocket-handling.mdx index bb6962c9..df60476e 100644 --- a/advanced/websocket-handling.mdx +++ b/advanced/websocket-handling.mdx @@ -1047,7 +1047,7 @@ Each stream error sets `is_logged_in = false` and fires the `shutdown_notifier` |------|--------|-------|-------------| | **401** | Disables auto-reconnect | `LoggedOut` | No — session invalid, must re-pair | | **409** | Disables auto-reconnect | `StreamReplaced` | No — prevents displacement loop | -| **429** | Adds 5 to backoff counter | None | Yes — extended Fibonacci backoff | +| **429** | Adds 5 to backoff counter | `StreamError` | Yes — extended Fibonacci backoff | | **503** | Normal handling | None | Yes — standard backoff | | **515** | Marks as expected disconnect | None | Yes — immediate, no backoff | | **516** | Disables auto-reconnect | `LoggedOut` | No — device removed | @@ -1119,7 +1119,7 @@ Maximum: 900s (15 minutes) Jitter: ±10% ``` -For rate-limited errors (429), the backoff counter is incremented by 5 before the normal increment, causing the delay to jump significantly on the next reconnection attempt. +For rate-limited errors (429), the client increments the backoff counter by 5 before the normal increment, causing the delay to jump significantly on the next reconnection attempt. Since [#1263](https://github.com/oxidezap/whatsapp-rust/pull/1263), the client also dispatches `Event::StreamError` for 429 — WA Web gives its UI no signal here at all (429 is outside the `500..600` range its handler special-cases), but an embedder has no UI to fall back on, so the rate limit is reported like every other coded stream error. **Stability-gated reset.** The backoff counter does not reset to its base immediately on a successful `` authentication. Instead, `connected_at_ms` records the auth time, and the counter only resets when the *next* disconnect finds the connection was stable for at least `STABLE_CONNECTION_RESET_MS` (30s) — matching WA Web's `resetDelay`. A connection that authenticates and then immediately drops keeps escalating the backoff instead of resetting to 1s and retrying in a tight loop. diff --git a/api/client.mdx b/api/client.mdx index c878cb40..19c8900b 100644 --- a/api/client.mdx +++ b/api/client.mdx @@ -495,7 +495,7 @@ The client handles specific `` codes from the WhatsApp server: |-------------------|---------|---------------|----------------| | **401** | Session invalidated (unauthorized) | `LoggedOut` | Disabled — must re-pair | | **409** | Another client connected (conflict) | `StreamReplaced` | Disabled — prevents displacement loop | -| **429** | Rate limited (too many connections) | None (emits `Disconnected`) | Yes, with extended backoff (+5 steps) | +| **429** | Rate limited (too many connections) | `StreamError`, then `Disconnected` | Yes, with extended backoff (+5 steps) | | **503** | Service unavailable | None | Yes, normal backoff | | **515** | Expected disconnect (e.g., post-pairing) | None | Yes, immediate (no backoff) | | **516** | Device removed | `LoggedOut` | Disabled — must re-pair | @@ -509,6 +509,8 @@ When you receive a `LoggedOut` or `StreamReplaced` event, auto-reconnect is perm When the server returns a 429 stream error, the client bumps the internal backoff counter by 5 Fibonacci steps before reconnecting. This means the reconnection delay jumps significantly (e.g., from ~1s to ~13s on the first rate limit) to respect the server's throttling. +As of [#1263](https://github.com/oxidezap/whatsapp-rust/pull/1263), the client also dispatches an `Event::StreamError` for 429 (code `"429"`). WhatsApp Web's own handler gives no UI signal for this case — it only special-cases `500..600`. An embedder has no UI to fall back on, so 429 is now reported the same way every other coded stream error is. This event fires in addition to `Event::Disconnected`, not instead of it. The 429 handler never marks the disconnect as expected, so the shared connection-loss path still dispatches `Disconnected` once the socket closes, the same as every other unexpected drop. + ### General reconnection behavior | Scenario | Behavior | diff --git a/concepts/events.mdx b/concepts/events.mdx index a486b997..980ba4c6 100644 --- a/concepts/events.mdx +++ b/concepts/events.mdx @@ -463,7 +463,7 @@ Event::LoggedOut(logout) => { ### StreamError -**Emitted:** For unrecognized stream error codes (codes not matching 401, 409, 429, 503, 515, or 516) +**Emitted:** For unrecognized stream error codes (codes not matching 401, 409, 429, 503, 515, or 516), and — since [#1263](https://github.com/oxidezap/whatsapp-rust/pull/1263) — also for `429` (rate-limited), even though 429 is itself a recognized, explicitly-handled code. WhatsApp Web's own handler has no arm for it either (only `500..600` is special-cased there), so reporting 429 here is an embedder-facing choice rather than a fidelity fix. ```rust #[derive(Debug, Clone, Serialize, bon::Builder)] @@ -477,15 +477,15 @@ pub struct StreamError { **Usage:** ```rust Event::StreamError(err) => { - eprintln!("Unknown stream error: {} (raw: {:?})", err.code, err.raw); + eprintln!("Stream error: {} (raw: {:?})", err.code, err.raw); } ``` -Recognized stream error codes emit specific events instead of `StreamError`: +Specific stream error codes have the following event behavior: - **401** → `LoggedOut` (session invalidated) - **409** → `StreamReplaced` (another client connected) -- **429** → No event emitted (reconnects with extended backoff) +- **429** → `StreamError` (rate limited; also reconnects with extended backoff) - **503** → No event emitted (reconnects with normal backoff) - **515** → No event emitted (immediate reconnect, e.g., after pairing) - **516** → `LoggedOut` (device removed)