feat(realtime): add support for protocol format 2.0.0 - #1397
Merged
Conversation
Implements Realtime protocol 2.0.0 for the Dart/Flutter SDK (SDK-583). - Add a `Serializer` ported from the supabase-js reference that encodes text frames as the positional JSON array `[join_ref, ref, topic, event, payload]` and handles binary frames (`userBroadcastPush` kind 3 encode, `userBroadcast` kind 4 decode) with the JSON/binary encoding flag and `allowedMetadataKeys`. - Default the connection to `vsn=2.0.0` and wire the serializer as the default encode/decode. - Decode incoming binary broadcast frames and send broadcasts whose payload is a `Uint8List`/`TypedData` as binary frames. - Loosen `RealtimeEncode`/`RealtimeDecode` to carry `String` or bytes and let the connection listener accept binary frames. - Rename the `conn` family to avoid abbreviations: `conn` -> `connection`, `connState` -> `connectionStatus`, `onConnMessage` -> `onConnectionMessage`, `_onConn*` -> `_onConnection*`. - Update realtime frame mocks/tests to the 2.0.0 array format and add `serializer_test.dart`. BREAKING CHANGE: the default Realtime protocol is now 2.0.0, and the `RealtimeClient.conn`/`connState` fields are renamed to `connection`/`connectionStatus`.
…allback The callback form was a port artifact from the realtime-js/Phoenix serializer. Both encode and decode are synchronous here, so return the result directly: RealtimeEncode is now Object Function(Map<String, dynamic>) and RealtimeDecode is Map<String, dynamic> Function(Object).
Remove the custom encode/decode constructor hook and the RealtimeEncode/ RealtimeDecode typedefs. With the protocol pinned to 2.0.0, a custom codec that does not emit valid frames would only break the connection, and the hook was unused. The client now holds a private Serializer directly. BREAKING CHANGE: the RealtimeClient encode/decode constructor parameters, the encode/decode fields, and the RealtimeEncode/RealtimeDecode typedefs are removed.
Add a RealtimeProtocolVersion enum (v1/v2) and a version constructor parameter on RealtimeClient, defaulting to v2. v1 uses the legacy object-shaped JSON frames; v2 uses the serializer. The enum carries the wire vsn string sent as the connection parameter. Also drop the porting references from the doc comments.
Re-add optional encode/decode constructor arguments (and the RealtimeEncode/RealtimeDecode typedefs). They default to the codec selected by version, so consumers can swap in a faster serializer.
- decode() validates the 2.0.0 text frame shape and throws a clear FormatException instead of an obscure cast error - onConnectionMessage drops malformed frames instead of crashing the connection, and guards the status log against non-map payloads - set the web binaryType explicitly to arraybuffer/list - document the ASCII assumption for binary frame string fields
…et constant Avoid copying frame slices during binary decode, name the userBroadcast header offset, and add an end-to-end test for dispatching a received binary broadcast to onBroadcast.
Write the header directly into the final frame buffer instead of allocating a separate header and copying it, removing one allocation and one copy per binary broadcast push.
Make encode/decode plain final fields resolved in the initializer list instead of late, using a shared static serializer and static legacy codec helpers. Removes the late modifier and a per-client serializer allocation.
spydon
force-pushed
the
lukasklingsbo/sdk-583-realtime-protocol-200
branch
from
June 12, 2026 12:10
512d1ce to
06f375d
Compare
Rename obj -> source in the serializer, msg -> message in the logger and log(), and res -> response in the channel HTTP helpers.
The mock_test suites now exercise v2; add a v1 decode + dispatch test so the legacy object-frame path keeps coverage alongside the existing v1 endpointURL and encode tests.
spydon
marked this pull request as ready for review
June 12, 2026 12:47
Keep the original conn/connState fields, the onConnMessage method, and the _onConn* handlers in this PR so it carries only the protocol 2.0.0 feature and RealtimeProtocolVersion. The breaking renames move to a follow-up PR targeting v3.
grdsdev
approved these changes
Jun 12, 2026
spydon
added a commit
that referenced
this pull request
Aug 6, 2026
) ## What Cleans up the connection-related naming on `RealtimeClient`. Split out of #1397 so that PR could carry only the protocol 2.0.0 feature without breaking changes. ### Spell out the `conn` abbreviation | Before | After | | --- | --- | | `RealtimeClient.conn` | `connection` | | `RealtimeClient.connState` | `connectionState` (see below) | | `RealtimeClient.onConnMessage` | `onConnectionMessage` | Private and local: `_onConnOpen` / `_onConnClose` / `_onConnError` became `_onConnectionOpen` / `_onConnectionClose` / `_onConnectionError`, and the `localConn` local in `connect()` became `localConnection`. Also the shortenings the rename itself did not reach: the heartbeat timeout log message now reads "Attempting to re-establish connection", a `channel_test` description that said "via ws conn", and `chanParams` on the test stub's `channel` override (renamed to `config`, matching the `RealtimeClient.channel` signature it overrides). ### Collapse the two socket-state accessors Spelling out `connState` left it one word away from the existing `String get connectionState`, and both carried the same information. The stringly typed getter is gone and the typed field takes the name: ```dart // before SocketStates? connState; String get connectionState => switch (connState) { ... }; // after SocketState? connectionState; ``` For the string form use `connectionState?.name`; for the common case `isConnected` is unchanged. ### Singularize the enum `SocketStates` is now `SocketState`, matching Dart convention and the large majority of the 51 enums in the repository. Consumers updated: `supabase_flutter` (`supabase.dart`, `lifecycle_test.dart`). ## Breaking changes - `RealtimeClient.conn` → `connection` - `RealtimeClient.onConnMessage` → `onConnectionMessage` - `RealtimeClient.connState` → `connectionState` - `RealtimeClient.connectionState` changes type from `String` to `SocketState?`. This one is a silent change for anyone reading it as a string, so it is worth calling out in the release notes. - `SocketStates` → `SocketState` Renames and one removal. No behaviour change. ## Capability matrix `RealtimeClient.connection`, `RealtimeClient.onConnectionMessage`, and `SocketState` are registered in `sdk-compliance.yaml`. `RealtimeClient.connectionState` keeps its name and its existing entry. ## Notes - Rebased onto `main` now that #1397 has merged. - The description previously said this would be retargeted to `v3` (tracked in #1278). It still targets `main`; happy to flip the base if that is still the plan. - The remaining plural enums are tracked in #1652. Only `PostgresTypes` among them is actually exported, so three of the four can land before v3; left out here to keep this PR scoped. ## Tests `realtime_client` 203 passing, `supabase` 135 passing, `supabase_flutter` 63 passing. `dart analyze` and `dart format` clean. The capability matrix symbol and schema checks were run locally against `main` and pass. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Breaking Changes** - Renamed realtime connection APIs for clearer terminology, including `SocketState`, `connection`, `connectionState`, and `onConnectionMessage`. - Removed the previous string-based connection-state getter. - Connection status is now represented directly through typed socket states. - **Bug Fixes** - Disconnecting now reliably cancels pending reconnect attempts and clears the active connection. - **Documentation** - Updated SDK capability information to reflect the renamed realtime APIs. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds Realtime protocol 2.0.0 support to the Dart/Flutter SDK and makes the protocol version selectable.
Closes SDK-583.
Protocol 2.0.0 changes the WebSocket frame format:
[join_ref, ref, topic, event, payload]instead of the 1.0.0 object layout, reducing JSON work on the backend (lower latency).Changes
Serializer (
serializer.dart, new)[join_ref, ref, topic, event, payload].userBroadcastPush, kind3).userBroadcast, kind4).allowedMetadataKeys.decodevalidates the text frame shape and throws a clearFormatExceptionon malformed input;onConnMessagelogs and drops such frames instead of crashing the connection.Selectable protocol version
RealtimeProtocolVersionenum (v1→1.0.0,v2→2.0.0); each carries thevsnvalue sent as the connection parameter.RealtimeClienttakes aversionparameter, defaulting tov2.v2uses the serializer;v1uses the legacy object-shaped JSON frames.Codec override
encode/decodeconstructor arguments (and theRealtimeEncode/RealtimeDecodetypedefs) let consumers swap in a custom serializer; they default to the codec for the selectedversion.RealtimeEncode=Object Function(Map<String, dynamic>),RealtimeDecode=Map<String, dynamic> Function(Object)(synchronous — async/isolate codec is a follow-up, see below).Binary broadcasts
Incoming binary broadcast frames are decoded into the same map shape as JSON broadcasts. To send binary, provide a
Uint8List(or anyTypedData) under thepayloadkey:```dart
channel.sendBroadcastMessage(event: 'file', payload: {'payload': myUint8List});
```
Binary frames are delivered as
Uint8Liston every platform; the web transport setsbinaryTypeto arraybuffer explicitly.Misc
obj/msg/res) in touched code.Breaking changes
2.0.0(passversion: RealtimeProtocolVersion.v1to keep the old behavior).RealtimeEncode/RealtimeDecodechanged from callback-based to return-based signatures.Related
conn→connection,connState→connectionStatus,onConnMessage→onConnectionMessage) are split into refactor(realtime)!: clean up connection naming on RealtimeClient #1404, which stacks on this PR and will be retargeted tov3after this merges.Tests
realtime_client: 121 passing (newserializer_test.dart, endpointURLv1, legacy encode, legacy decode + dispatch, custom-encodeoverride, malformed-frame, binary-broadcast receive).supabase: 83 passing.supabase_flutter/test/lifecycle_test.dart: analyze clean.