Skip to content

fix: realtime protocol correctness (join, ack, heartbeat, tls) - #42

Merged
zoedsoupe merged 2 commits into
mainfrom
feat/rt-protocol-correctness
Oct 16, 2026
Merged

zoedsoupe merged 2 commits into
mainfrom
feat/rt-protocol-correctness

Conversation

@zoedsoupe

Copy link
Copy Markdown
Member

Problem

The client could not hold a subscription correctly:

  • The registry called a send_message/2 that does not exist, so every subscribe and unsubscribe crashed it.
  • The postgres_changes join payload had the wrong wire shape (raw binding maps, lowercase events).
  • Incoming CDC payloads were read at the wrong nesting level, so type transforms never ran.
  • Broadcasts were dispatched by the outer event name, so named bindings never matched.
  • Token resolution read client.apikey, which does not exist, raising a KeyError in the common anon-key case.
  • There was no rejoin after reconnect: channels stayed :joined locally while the server had nothing.
  • The ack ref was dropped before encoding, so wait_for_ack/2 always timed out.
  • Heartbeats scheduled duplicate timers, had no dead-connection detection, and used a wrong backoff formula.
  • TLS verification was hardcoded to verify_none.

Solution

Fix the join flow (route sends through the Connection, flat join payload with uppercase events and access token, correct incoming nesting, dispatch by inner event, rejoin on reconnect), the ack flow (preserve the ack ref, own the timeout timer in the Connection), the connection lifecycle (single heartbeat timer, dead-heartbeat detection, stepped backoff, unified down path, stale-message guards), token resolution, and TLS (verify_peer with system CAs and a hostname check, plus a :tls_opts escape hatch). Give each client its own ETS table and persist the auth token across reconnects.

Rationale

Each item is a correctness bug against the phoenix/realtime protocol, so the fixes follow the server semantics rather than patching symptoms. Joins and leaves get dedicated Connection calls because the generic send path buffers anything sent while a channel is joining, which would buffer the join itself. Late postgres_changes and presence bindings now return {:error, :already_joining | :already_joined}, matching realtime-js 2.101+, because phoenix has no add-binding push and buffering them has no correct delivery mechanism.

@zoedsoupe
zoedsoupe added this pull request to stack #45 October 16, 2026 01:44
@zoedsoupe
zoedsoupe merged commit 73548ac into main Oct 16, 2026
24 checks passed
@zoedsoupe
zoedsoupe deleted the feat/rt-protocol-correctness branch October 16, 2026 22:22
@zoedsoupe zoedsoupe mentioned this pull request Oct 16, 2026
zoedsoupe added a commit that referenced this pull request Oct 19, 2026
🚀 Want to release this?
---


##
[0.6.0](v0.5.0...v0.6.0)
(2026-10-16)


### Features

* filter builder, select, heartbeat telemetry and presence fixes
([#44](#44))
([6fa0dae](6fa0dae))
* rejoin backoff, duplicate binding dedup and aligned binding ids
([#46](#46))
([ce458d9](ce458d9))
* serializer behaviour and vsn 2.0.0 binary framing
([#43](#43))
([3aec1d6](3aec1d6))


### Bug Fixes

* realtime protocol correctness (join, ack, heartbeat, tls)
([#42](#42))
([73548ac](73548ac))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
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.

1 participant