HopeFlow Realtime is a Cloudflare Worker + Durable Object pair that provides per-user WebSocket fan-out with a simple authenticated publish API. Browser or server clients connect over WebSockets, while trusted backends can publish JSON envelopes that are delivered to every active socket for that user.
- Outer Worker (
src/index.ts) authenticates requests with a sharedJWT_SECRET. WebSocket upgrades are routed to the user’s Durable Object, whilePOST /publishaccepts JSON payloads and hands them off for delivery. - Durable Object (
HopeFlowRealtime) maintains all live WebSocket sessions for a single user. It supports:WS /connect(invoked by the outer worker with an addedX-User-IDheader).POST /deliverfor canonical envelopes dispatched from/publish.- Automatic hibernation up to 1 hour to minimize cold starts.
- JSON responses include permissive CORS headers (
*origin) so dashboards or browser tooling can hit/publishwhen authorized.
- Node 18+ (Cloudflare Workers runtime target)
pnpmfor dependency management- Wrangler CLI ≥ v4.45 (already listed in devDependencies)
pnpm install # install deps
pnpm dev # wrangler dev (defaults to local mode)
pnpm deploy # wrangler deploy to your account
pnpm cf-typegen # optional: regenerate type bindingsWrangler reads secrets from .dev.vars during pnpm dev. At minimum define:
JWT_SECRET=super-long-random-string
For production, run wrangler secret put JWT_SECRET and ensure the Durable Object binding name HOPEFLOW_REALTIME matches the one declared in wrangler.jsonc.
- Endpoint:
wss://<worker-domain>/ - Headers:
Authorization: Bearer <JWT>(payload must include auserIdstring). Browsers may also pass?token=<JWT>query param. - Subscriptions: After connect, clients must send
{"type":"subscribe","filters":[{ "type": "chat_message", "questId": "q1", "nodeId": "n1" }]}. Filters match exactly on provided keys against the envelope:typecompares to the envelopetype, other keys compare to fields inside the JSONpayload. Add filters withsubscribe, remove with{"type":"unsubscribe","filters":[...]}. - Acks: For each delivered envelope, clients should respond with
{"type":"ack","envelopeId":"<id>","result":<any>}. Results are returned to the/publishcaller. - Behavior: Server replies
pongtoping(case-insensitive). Only sockets with at least one matching filter receive a message.
- Endpoint:
POST https://<worker-domain>/publish - Headers:
Authorization: Bearer <JWT>(or?token=<JWT>query param),Content-Type: application/json - Body:
{
"userId": "user_123",
"type": "message", // optional; defaults to "message"
"payload": { "any": "json" }
}- Rules: Multi-tenant publish is allowed;
userIdin the body is the delivery target.envelopeIdis generated for you; you don’t need to supply one. Requests without JSON bodies receive error responses. - Response:
{"clientCount": <delivered-to>, "results": [<ack results>...]}. The worker waits up to 5s for client acknowledgments and returns whatever arrived;clientCountreflects sockets that matched at least one subscription filter. - CORS:
OPTIONS /publishis preflight-enabled with*origin for browser usage. - Ack behavior: Each delivered envelope should be acked with
{"type":"ack","envelopeId":"<id>","result":<any>}. Ifresultis omitted, the server recordsnullfor that client in the returnedresultsarray. - Subscription rules: Filters are exact-match and must be objects with string values (including
type). Up to 50 filters are processed per subscribe/unsubscribe message.
Tokens are verified with HS256 using JWT_SECRET. The payload must include a non-empty userId string. Other claims are passed through untouched and can be extended later.
- Update
wrangler.jsoncwith your Cloudflare account details (routes, D1/queues, etc.) as needed. - The included migration (
tag: v1) registers the Durable Object class; runwrangler deploy --migrationsthe first time or whenever tags change. - Use Smart Placement or other placement settings if latency-sensitive (commented template already in
wrangler.jsonc).
- Unauthorized (401): Check that the Authorization header is present and the token contains
userId. - Unsupported Media Type (415): Ensure
Content-Type: application/jsonon/publish. - Delivery count < active clients: The Durable Object logs errors when sockets fail; inspect Cloudflare tail logs (
wrangler tail) for per-user traces.