[Durable Objects]

SessionDO WebSocket relay: one Binance upstream per session, fan-out to browser clients.

Durable Objects

Abstract

SessionDO (frontend/worker/src/durable-objects/session.ts) is a per-session WebSocket relay. Browsers connect to the Worker at /api/stream; the Worker pins a Durable Object by session name and the DO opens one upstream Binance kline socket, broadcasting payloads to connected clients.

Goal: survive browser per-origin WebSocket connection limits and share upstream subscriptions.

Conceptual model

Interface surface

Client → Worker

wss://<worker>/api/stream?session=<name>&symbol=BTCUSDT&interval=1m

frontend/worker/src/index.ts:

  • Requires env.SESSIONS binding; else 503 NO_DO.
  • idFromName(session || 'default').
  • Rewrites request to DO path /ws?… and returns upgrade response.

DO HTTP surface

PathRole
/wsOnly path; requires Upgrade: websocket (else 426)

Query: symbol (default BTCUSDT), interval (default 1m).

Client → DO messages (JSON)

MessageEffect
{ "action": "subscribe", "symbol", "interval" }Rebind upstream if changed
{ "action": "ping" }{ action: "pong", t }

DO → client

  • Raw Binance kline JSON (same as direct venue WS).
  • Status: { type: 'status', state: 'open'|'closed', url? }
  • Errors: { type: 'error', message }

Example plugin

frontend/public/plugins/example-cf-do-stream.js maps kline fields to Bar and builds the Worker URL from config endpoint.

Internals

TopicDetail
Upstream URLwss://stream.binance.com:9443/ws/{symbol}@kline_{interval}
Client lifecycleacceptWebSocket; on last client close → closeUpstream
HibernationComment intent: hibernate when empty; implementation uses explicit close
Fan-outbroadcast to all clients

wrangler binding (often commented until ready)

# [[durable_objects.bindings]]
# name = "SESSIONS"
# class_name = "SessionDO"

# [[migrations]]
# tag = "v1"
# new_sqlite_classes = ["SessionDO"]

SessionDO is re-exported from src/index.ts for the Workers runtime class registry.

Invariants & edge cases

  1. Session key — example plugin uses symbol@interval as session name so like-minded tabs share one DO.
  2. No auth on stream today — treat as public relay of public market data; do not put secrets in query strings.
  3. Upstream hard-coded Binance — not a generic multiprovider DO yet.
  4. Binding optional in local — without SESSIONS, stream API fails closed with NO_DO.

Failure modes

SymptomCause
503 NO_DOBinding commented / not deployed
426 expected websocketNon-upgrade GET
No barsUpstream block or wrong interval string
Stale symbolClient did not send subscribe after change

See also