Skip to content

Commit b651103

Browse files
committed
feat(websocket): expose heartbeat events and consolidate examples
1 parent 791c845 commit b651103

File tree

5 files changed

+78
-4
lines changed

5 files changed

+78
-4
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,27 @@ The same can be done without using decorators:
238238
bfx.wss.on("candles_update", callback=on_candles_update)
239239
```
240240

241+
### Heartbeat events
242+
243+
The WebSocket server sends periodic heartbeat messages to keep connections alive.
244+
These are now exposed as `heartbeat` events that users can listen to:
245+
246+
```python
247+
@bfx.wss.on("heartbeat")
248+
def on_heartbeat(subscription=None):
249+
if subscription:
250+
# Heartbeat for a specific subscription (public channels)
251+
print(f"Heartbeat for {subscription['channel']}")
252+
else:
253+
# Heartbeat for authenticated connection
254+
print("Heartbeat on authenticated channel")
255+
```
256+
257+
For public channel subscriptions, the heartbeat event includes the subscription information.
258+
For authenticated connections, heartbeats are sent without subscription data.
259+
260+
---
261+
241262
# Advanced features
242263

243264
## Using custom notifications

bfxapi/websocket/_client/bfx_websocket_bucket.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,11 @@ async def start(self) -> None:
6666
if (
6767
(chan_id := cast(int, message[0]))
6868
and (subscription := self.__subscriptions.get(chan_id))
69-
and (message[1] != Connection._HEARTBEAT)
7069
):
71-
self.__handler.handle(subscription, message[1:])
70+
if message[1] == Connection._HEARTBEAT:
71+
self.__event_emitter.emit("heartbeat", subscription)
72+
else:
73+
self.__handler.handle(subscription, message[1:])
7274

7375
def __on_subscribed(self, message: Dict[str, Any]) -> None:
7476
chan_id = cast(int, message["chan_id"])

bfxapi/websocket/_client/bfx_websocket_client.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,9 +266,11 @@ async def __connect(self) -> None:
266266
if (
267267
isinstance(message, list)
268268
and message[0] == 0
269-
and message[1] != Connection._HEARTBEAT
270269
):
271-
self.__handler.handle(message[1], message[2])
270+
if message[1] == Connection._HEARTBEAT:
271+
self.__event_emitter.emit("heartbeat", None)
272+
else:
273+
self.__handler.handle(message[1], message[2])
272274

273275
async def __new_bucket(self) -> BfxWebSocketBucket:
274276
bucket = BfxWebSocketBucket(self._host, self.__event_emitter)

bfxapi/websocket/_event_emitter/bfx_event_emitter.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
_COMMON = [
3434
"disconnected",
35+
"heartbeat",
3536
"t_ticker_update",
3637
"f_ticker_update",
3738
"t_trade_execution",

examples/websocket/heartbeat.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
"""
2+
python -c "import examples.websocket.heartbeat"
3+
4+
Single example demonstrating heartbeat handling for both public and authenticated flows.
5+
6+
If BFX_API_KEY and BFX_API_SECRET are set in the environment, the client will
7+
authenticate and you will see heartbeats on the authenticated (chan 0) path
8+
as well. Otherwise, only public channel heartbeats are shown.
9+
"""
10+
11+
import os
12+
from typing import Optional
13+
14+
from bfxapi import Client
15+
from bfxapi.websocket.subscriptions import Subscription
16+
17+
18+
bfx = Client(
19+
api_key=os.getenv("BFX_API_KEY"),
20+
api_secret=os.getenv("BFX_API_SECRET"),
21+
)
22+
23+
24+
@bfx.wss.on("heartbeat")
25+
def on_heartbeat(subscription: Optional[Subscription] = None):
26+
if subscription:
27+
channel = subscription["channel"]
28+
label = subscription.get("symbol", subscription.get("key", "N/A"))
29+
print(f"Heartbeat for {channel}: {label}")
30+
else:
31+
print("Heartbeat on authenticated connection")
32+
33+
34+
@bfx.wss.on("authenticated")
35+
async def on_authenticated(event):
36+
print(f"Authenticated: userId={event.get('userId')}")
37+
38+
39+
@bfx.wss.on("open")
40+
async def on_open():
41+
# Subscribe to a public channel to observe subscription heartbeats
42+
await bfx.wss.subscribe("ticker", symbol="tBTCUSD")
43+
44+
45+
print("Starting WebSocket client... Press Ctrl+C to stop.")
46+
bfx.wss.run()
47+
48+

0 commit comments

Comments
 (0)