Live data: WebSockets, events, webhooks, SignalR
Data that keeps arriving: a WebSocket, a stream of server events, a webhook, a SignalR hub. Each one is a LiveFeed: the connection lives on its own thread, messages wait in a queue, and Draw() takes them when it's ready -- it never waits for the network. Feeds reconnect by themselves when the connection drops and close when the sketch stops.
On this page · 6 entries
class LiveFeed<T> · feed.Next(out msg) · feed.ReadAll(max)
What every feed has. Next(out msg) takes the oldest waiting message (false when there's none); ReadAll() takes them all, oldest first -- or at most max, leaving the rest for the next frame. State (Connecting, Open, Reconnecting, Closed, Failed), Connected, Error, a ready-made Status line, Received, PerSecond and Pending. A firehose can't eat the memory: past Capacity (10,000) waiting messages, the oldest are dropped and counted in Dropped. Close() ends it.
ConnectWebSocket
ConnectWebSocket(url, reconnect) → WebSocketFeed
A WebSocket (ws:// or wss://): each text message arrives as a string -- usually JSON, for JSONObject.Parse(). feed.Send(text) sends one back (or a JSONObject); sent at once when connected, otherwise as soon as it is. Exchanges' trade streams, Bluesky's Jetstream, live chats, your own servers.
ListenEvents
ListenEvents(url, reconnect) → LiveFeed<ServerEvent>
A stream of server-sent events (SSE, text/event-stream): Wikipedia's live edits, Firebase, .NET 10's TypedResults.ServerSentEvents. Each ServerEvent has its Type ("message" unless the server names it), Data (multi-line data joined), Id, and JSON(). When it reconnects it tells the server the last id it saw, so nothing is missed, and it honors the server's retry:.
ListenWebhook
ListenWebhook(port, path) → LiveFeed<WebhookCall>
A small web server on your computer, at http://localhost:port/path/: every request it gets becomes a WebhookCall -- Method, Path, Query, Headers, Body and JSON() -- and is answered 200 OK. Point GitHub, Stripe or ntfy at it through a tunnel (smee.io, ngrok) and every star, payment or message lands in the sketch.
ConnectSignalR
ConnectSignalR(hubUrl, reconnect) → SignalRFeed
An ASP.NET Core SignalR hub (http(s)://host/hub), spoken directly -- SignalR's JSON protocol over a WebSocket, no packages. Whatever the hub sends to its clients (Clients.All.SendAsync("Target", args...)) arrives as a HubMessage: its Target and Arguments, with Object(i), Text(i) and Number(i). hub.Invoke("Method", args...) calls a method on the hub. It handshakes, keeps itself alive with pings and reconnects by itself.
Runnable example
A complete sketch using this section. Test in Dana opens it in the IDE, or paste it into a new tab and press Run.
// Wikipedia, live: every edit made anywhere, as it happens (Wikimedia's public // event stream, no key). Each edit falls as its page title; the bigger the change, // the bigger the word. New pages are green, edits by bots are dim. public class WikiLive : Sketch { LiveFeed<ServerEvent> edits; readonly List<(string Title, float X, float Y, float Size, Color C)> words = new(); public override void Setup() { Size(900, 560); edits = ListenEvents("https://stream.wikimedia.org/v2/stream/recentchange"); } public override void Draw() { Background(12, 14, 22); // Everything that arrived since the last frame (at most 40, the rest wait). foreach (var e in edits.ReadAll(40)) { var edit = e.JSON(); if (edit.GetString("type") is not ("edit" or "new")) continue; var length = edit.GetJSONObject("length"); float change = length == null ? 0 : Abs(length.GetFloat("new") - length.GetFloat("old")); var c = edit.GetString("type") == "new" ? Color(120, 230, 150) : edit.GetBoolean("bot") ? Color(110, 120, 150) : Color(230, 235, 255); words.Add((edit.GetString("title"), Random(40, Width - 40), -10, Constrain(9 + Sqrt(change), 9, 48), c)); } TextAlign(TextAlignH.Center, TextAlignV.Center); for (int i = words.Count - 1; i >= 0; i--) { var w = words[i]; w.Y += 1.2f + w.Size * 0.03f; if (w.Y > Height + 20) { words.RemoveAt(i); continue; } words[i] = w; Fill(w.C); TextSize(w.Size); Text(w.Title, w.X, w.Y); } Fill(255, 255, 255, 140); TextAlign(TextAlignH.Left, TextAlignV.Top); TextSize(13); Text($"Wikipedia, live · {edits.Status} · {edits.Received:N0} events", 16, 14); } }