Skip to content

feat(ws): WebSocket endpoint for real-time price streaming#62

Merged
prodbycorne merged 1 commit into
SmartDropLabs:mainfrom
stableprogrammer:feat/issue-22
Jun 29, 2026
Merged

feat(ws): WebSocket endpoint for real-time price streaming#62
prodbycorne merged 1 commit into
SmartDropLabs:mainfrom
stableprogrammer:feat/issue-22

Conversation

@stableprogrammer

Copy link
Copy Markdown
Contributor

Closes #22

Summary

  • src/ws/PriceSubscriptionManager.js — core subscription registry: max 5 assets per client, max 100 concurrent connections, notifyPriceUpdates() diffing with 0.1% threshold, heartbeat ping/pong every 30 s, disconnects sockets that miss 3 consecutive pings, increments/decrements ws_connections_current Prometheus gauge (gracefully skipped when prom-client is not installed).

  • src/ws/priceWebSocket.js — attaches ws.Server to the existing HTTP server at /ws; starts the heartbeat on attach.

  • src/index.js — calls priceWebSocket.attach(server) after http.listen(); stops heartbeat on SIGTERM/SIGINT.

  • src/jobs/priceRefresh.js — collects the freshPrices map returned by refreshAllCachedPrices() and passes it to subscriptionManager.notifyPriceUpdates() after each cron cycle.

  • src/services/priceOracle.jsrefreshAllCachedPrices() now returns { assetKey → { price, source } } so the job can forward diffs to the WS layer.

  • test/priceWebSocket.test.js — 7 integration tests (all passing): subscribe confirmation, asset cap at 5, price_update push on >0.1% change, no push below threshold, unsubscribe, invalid JSON error frame, connection count tracking.

Protocol

Client → Server: { "action": "subscribe", "assets": ["XLM", "USDC:GA5Z..."] }
Server → Client: { "type": "subscribed", "assets": ["XLM"] }

Server → Client: { "type": "price_update", "asset": "XLM", "price_usd": 0.1145,
                   "previous_price_usd": 0.112, "change_pct": 2.23,
                   "source": "stellar_dex", "timestamp": "..." }

Server → Client: { "type": "ping" }
Client → Server: { "action": "pong" }

Test plan

  • 7 unit/integration tests pass (test/priceWebSocket.test.js)
  • Connect to ws://localhost:3000/ws, subscribe to ["XLM"], trigger a price refresh — verify price_update frame arrives
  • Verify no frame is sent when price changes < 0.1%
  • Let 3 ping cycles elapse without sending pong — verify socket is terminated
  • Connect 100 clients — 101st should be rejected with close code 1013

Adds a /ws WebSocket server that pushes price updates to subscribers
without requiring clients to poll the REST endpoints.

- src/ws/PriceSubscriptionManager.js: tracks per-socket asset subscriptions
  (max 5 assets/client, max 100 concurrent connections); delivers price_update
  frames when a price changes >0.1%; heartbeat ping every 30 s, disconnects
  sockets that miss 3 consecutive pings; increments/decrements the
  ws_connections_current Prometheus gauge (no-op when prom-client absent)

- src/ws/priceWebSocket.js: attaches ws.Server to the existing HTTP server
  at path /ws and starts the heartbeat

- src/index.js: attaches WebSocket server after http.listen(); stops
  heartbeat on SIGTERM/SIGINT

- src/jobs/priceRefresh.js: collects fresh prices returned by
  refreshAllCachedPrices() and forwards them to subscriptionManager
  after each cron cycle

- src/services/priceOracle.js: refreshAllCachedPrices() now returns a
  freshPrices map { assetKey → { price, source } } for the WS layer

- test/priceWebSocket.test.js: 7 integration tests covering subscribe,
  asset cap, price_update push, no-push below threshold, unsubscribe,
  invalid JSON, and connection count tracking

Closes SmartDropLabs#22
@prodbycorne prodbycorne merged commit 7eedc5b into SmartDropLabs:main Jun 29, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement WebSocket endpoint for real-time price streaming

2 participants