Streaming API: multiplexed WebSocket connection (#3) - #141
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add Mastodon's dedicated streaming container to the CI compose stack, proxy /api/v1/streaming through nginx with WebSocket upgrade headers, boot the new service from setup_mastodon.sh, and add a live integration test covering Hunter.Streaming.health?/2, connect/2, and close/1 against a real user-stream update.
Fix an http(s) url: override crashing Connection.init/1 (schemes/1
only matches ws/wss) instead of returning {:error, reason} as
documented, and note the OTP 26+ floor in the moduledoc. Also:
tidy connect/2 and handle_call(:close, ...), relocate the private
ws_uri/2 helper, drop a no-op map key, document close/1's :noproc
exit on repeat calls, switch the test WebSocket server to
start_supervised!, and add regression tests for the url normalization
and for events with no "stream" key.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@claude review once. |
|
Claude finished @milmazz's task in 3m 11s —— View job Review: Streaming API (multiplexed WebSocket) — PR #141I read through Already addressed: your own review suggestion collapsing the redundant Minor observations (no blocking issues found):
Nothing here blocks merging — the connection lifecycle, event parsing (including the I did not run |
Adds Hunter.Streaming with a multiplexed WebSocket connection, event frame parser, and health check. Removes Hunter.EventStream. Includes a live CI integration test against a real Mastodon streaming service. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Closes #3. The last item of the Mastodon 4.6 API parity effort's user-facing backlog; builds on the Req/Mint stack from #103.
What
Real-time events over Mastodon's multiplexed streaming WebSocket (3.3+), WebSocket-only by design (SSE stays out of scope):
Hunter.Streaming—connect/2(options:streams:,subscriber:,url:override for split streaming hosts,transport_opts:), runtimesubscribe/3/unsubscribe/3, gracefulclose/1, andhealth?/2(plain-text endpoint, bypasses the JSON transformer). Requires Erlang/OTP 26+.Hunter.Streaming.Connection— one GenServer overmint_web_socket(new runtime dep; mint was already in the tree) owning the Mint conn + WebSocket state. Handshake happens synchronously ininit/1, soconnect/2returns{:error, reason}cleanly with no orphan process. Server pings are answered, never surfaced. No auto-reconnect: a single{:hunter_stream, pid, {:closed, reason}}(:local|{:remote, code}|{:error, term}) is delivered and the process exits:normal— callers supervise.Hunter.Streaming.Event— pure frame parser; payloads decode through the existing transformer (update/status.update→Status,notification,conversation,announcement,announcement.reaction→ new:announcement_reactiontarget, deletes → id string). Unknown event types are delivered with the raw payload for forward compatibility.Hunter.EventStream(breaking): the 2017 SSE frame struct was never wired to anything.Testing
bandit+websock_adapter, test-only): real handshakes through mint_web_socket — token on the upgrade request, control frames on the wire, event delivery, ping→pong, all three close paths monitor-verified. 213 unit tests, 0 failures; format/credo/dialyzer clean.mastodon-streamingcontainer and nginx WebSocket proxying; new live integration test (connect to the user stream, post a status, assert the update event) passed 25/25 against Mastodon v4.3.8.Notable: dialyzer cleanliness required routing around a
mint_web_socket1.0.5 typespec bug (its@opaque tdeclaresfragment: tuple()while defaulting tonil, which erasesnew/4's ok-branch) — we callnew/5with the documented defaultmode: :active, with a load-bearing comment at the call site.Spec:
docs/superpowers/specs/2026-07-10-streaming-design.md· Plan:docs/superpowers/plans/2026-07-10-streaming.md🤖 Generated with Claude Code