Skip to content

Commit 2bd13d5

Browse files
committed
feat(ws): include active subscriptions in ConnectionLost for reconnection
BREAKING CHANGE: StreamMessage::ConnectionLost now has two fields: `reason` and `subscriptions`. Update pattern matches accordingly. - Add `subscriptions: Vec<(Channel, Vec<String>)>` to ConnectionLost containing channels and markets active at disconnect time - Move SubscriptionState and SharedSubscriptions to session module - Simplify session constructors into single connect() method - Improve reconnection documentation with resubscription examples - Add CHANGELOG.md - Bump version to 0.2.0
1 parent 43f4a2d commit 2bd13d5

File tree

13 files changed

+149
-109
lines changed

13 files changed

+149
-109
lines changed

CHANGELOG.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [0.2.0] - 2026-01-18
9+
10+
### Added
11+
12+
- `ConnectionLost` now includes a `subscriptions` field containing the channels and
13+
markets that were active at the time of disconnection. This enables automatic
14+
resubscription after reconnecting.
15+
16+
### Changed
17+
18+
- **Breaking:** `StreamMessage::ConnectionLost` variant now has two fields: `reason`
19+
and `subscriptions`. Update pattern matches from `ConnectionLost { reason }` to
20+
`ConnectionLost { reason, .. }` or `ConnectionLost { reason, subscriptions }`.
21+
- Internal: `SubscriptionState` and `SharedSubscriptions` types moved from `client`
22+
module to `session` module and made public for internal sharing.
23+
- Internal: Simplified `KalshiStreamSession` constructors by consolidating
24+
`connect()`, `connect_with_health()`, and `connect_with_ready()` into a single
25+
`connect()` method. This is not a breaking change as `KalshiStreamSession` is
26+
not part of the public API.
27+
28+
### Fixed
29+
30+
- Improved documentation for WebSocket reconnection patterns with examples showing
31+
how to use the new `subscriptions` field.
32+
33+
## [0.1.0] - 2026-01-15
34+
35+
### Added
36+
37+
- Initial release
38+
- REST API client with full endpoint coverage
39+
- WebSocket streaming client with subscription management
40+
- RSA-PSS authentication
41+
- Typed `DisconnectReason` enum for connection lifecycle events
42+
- Health monitoring with configurable ping/pong timeouts
43+
- Connection strategies: `Simple` (fast-fail) and `Retry` (exponential backoff)
44+
- Support for all public and authenticated WebSocket channels

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "kalshi-trade-rs"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
edition = "2024"
55
authors = ["pbeets"]
66
description = "Rust client for the Kalshi trading API and WebSocket streams"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
8080
StreamMessage::Trade(t) => {
8181
println!("[TRADE] {} {} @ {}¢", t.market_ticker, t.count, t.yes_price);
8282
}
83-
StreamMessage::ConnectionLost { reason } => {
83+
StreamMessage::ConnectionLost { reason, .. } => {
8484
println!("Connection lost: {}", reason);
8585
break;
8686
}

examples/stream_lifecycle.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
107107
println!("[CLOSED] {}", reason);
108108
break;
109109
}
110-
StreamMessage::ConnectionLost { reason } => {
110+
StreamMessage::ConnectionLost { reason, .. } => {
111111
println!("[CONNECTION LOST] {}", reason);
112112
break;
113113
}

examples/stream_reconnect.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,12 @@ async fn run_stream(config: &KalshiConfig) -> Result<String, Box<dyn std::error:
5858
loop {
5959
match handle.update_receiver.recv().await {
6060
Ok(update) => match &update.msg {
61-
StreamMessage::Closed { reason } => return Ok(reason.clone()),
62-
StreamMessage::ConnectionLost { reason } => {
61+
StreamMessage::Closed { reason } => return Ok(reason.to_string()),
62+
StreamMessage::ConnectionLost { reason, subscriptions } => {
63+
eprintln!("Connection lost: {reason}");
64+
if !subscriptions.is_empty() {
65+
eprintln!("Lost subscriptions: {subscriptions:?}");
66+
}
6367
return Err(format!("Connection lost: {reason}").into());
6468
}
6569
StreamMessage::Fill(f) => {

examples/stream_ticker.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
117117
println!("[CLOSED] {}", reason);
118118
break;
119119
}
120-
StreamMessage::ConnectionLost { reason } => {
120+
StreamMessage::ConnectionLost { reason, .. } => {
121121
println!("[CONNECTION LOST] {}", reason);
122122
break;
123123
}

examples/stream_user_channels.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
8383
println!("[CLOSED] {}", reason);
8484
break;
8585
}
86-
StreamMessage::ConnectionLost { reason } => {
86+
StreamMessage::ConnectionLost { reason, .. } => {
8787
println!("[CONNECTION LOST] {}", reason);
8888
break;
8989
}

src/ws.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
//! StreamMessage::Ticker(data) => {
3030
//! println!("{}: {}¢", data.market_ticker, data.price);
3131
//! }
32-
//! StreamMessage::ConnectionLost { reason } => {
32+
//! StreamMessage::ConnectionLost { reason, .. } => {
3333
//! eprintln!("Connection lost: {}", reason);
3434
//! break; // Handle reconnection (see below)
3535
//! }
@@ -99,7 +99,7 @@
9999
//! StreamMessage::Ticker(data) => {
100100
//! println!("{}: {}¢", data.market_ticker, data.price);
101101
//! }
102-
//! StreamMessage::ConnectionLost { reason } => {
102+
//! StreamMessage::ConnectionLost { reason, .. } => {
103103
//! eprintln!("Connection lost: {}", reason);
104104
//! break; // Exit inner loop to reconnect
105105
//! }

src/ws/README.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ println!("Ticker markets: {:?}", handle.markets(Channel::Ticker));
139139

140140
### Reconnection Pattern
141141

142+
When a connection is lost unexpectedly, `ConnectionLost` includes the subscriptions
143+
that were active at the time of disconnection. Use this to resubscribe after reconnecting:
144+
142145
```rust
143146
use kalshi_trade_rs::ws::{ConnectStrategy, KalshiStreamClient, StreamMessage};
144147

@@ -152,8 +155,23 @@ let mut handle = client.handle();
152155
loop {
153156
match handle.update_receiver.recv().await {
154157
Ok(update) => match &update.msg {
155-
StreamMessage::ConnectionLost { reason } => {
156-
// Reconnect logic here
158+
StreamMessage::ConnectionLost { reason, subscriptions } => {
159+
eprintln!("Connection lost: {reason}");
160+
// subscriptions: Vec<(Channel, Vec<String>)>
161+
// Contains channels and their markets that were subscribed
162+
// before disconnect. Use this to resubscribe after reconnecting:
163+
//
164+
// let new_client = KalshiStreamClient::connect(&config).await?;
165+
// let mut new_handle = new_client.handle();
166+
// for (channel, markets) in subscriptions {
167+
// let market_refs: Vec<&str> = markets.iter().map(|s| s.as_str()).collect();
168+
// new_handle.subscribe(channel, &market_refs).await?;
169+
// }
170+
break;
171+
}
172+
StreamMessage::Closed { reason } => {
173+
// Clean close (client-initiated or server close frame)
174+
println!("Connection closed: {reason}");
157175
break;
158176
}
159177
_ => { /* process update */ }

0 commit comments

Comments
 (0)