Skip to content

feat(realtime): add support for protocol format 2.0.0 - #1397

Merged
spydon merged 16 commits into
mainfrom
lukasklingsbo/sdk-583-realtime-protocol-200
Jun 12, 2026
Merged

feat(realtime): add support for protocol format 2.0.0#1397
spydon merged 16 commits into
mainfrom
lukasklingsbo/sdk-583-realtime-protocol-200

Conversation

@spydon

@spydon spydon commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

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:

  • Text frames use the positional JSON array [join_ref, ref, topic, event, payload] instead of the 1.0.0 object layout, reducing JSON work on the backend (lower latency).
  • Binary frames allow raw binary payloads for broadcast user events.

Changes

Serializer (serializer.dart, new)

  • Text encode/decode as the positional array [join_ref, ref, topic, event, payload].
  • Binary encode for broadcast pushes (userBroadcastPush, kind 3).
  • Binary decode for incoming broadcasts (userBroadcast, kind 4).
  • JSON/binary encoding flag and allowedMetadataKeys.
  • decode validates the text frame shape and throws a clear FormatException on malformed input; onConnMessage logs and drops such frames instead of crashing the connection.

Selectable protocol version

  • New RealtimeProtocolVersion enum (v11.0.0, v22.0.0); each carries the vsn value sent as the connection parameter.
  • RealtimeClient takes a version parameter, defaulting to v2. v2 uses the serializer; v1 uses the legacy object-shaped JSON frames.

Codec override

  • Optional encode / decode constructor arguments (and the RealtimeEncode / RealtimeDecode typedefs) let consumers swap in a custom serializer; they default to the codec for the selected version.
  • Return-based signatures: 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 any TypedData) under the payload key:

```dart
channel.sendBroadcastMessage(event: 'file', payload: {'payload': myUint8List});
```

Binary frames are delivered as Uint8List on every platform; the web transport sets binaryType to arraybuffer explicitly.

Misc

  • Expanded a few abbreviated identifiers (obj/msg/res) in touched code.

Breaking changes

  • Default Realtime protocol is now 2.0.0 (pass version: RealtimeProtocolVersion.v1 to keep the old behavior).
  • RealtimeEncode / RealtimeDecode changed from callback-based to return-based signatures.

Related

Tests

  • realtime_client: 121 passing (new serializer_test.dart, endpointURL v1, legacy encode, legacy decode + dispatch, custom-encode override, malformed-frame, binary-broadcast receive).
  • supabase: 83 passing.
  • supabase_flutter/test/lifecycle_test.dart: analyze clean.

@github-actions github-actions Bot added the realtime This issue or pull request is related to realtime label Jun 12, 2026
spydon added 13 commits June 12, 2026 14:08
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
spydon force-pushed the lukasklingsbo/sdk-583-realtime-protocol-200 branch from 512d1ce to 06f375d Compare June 12, 2026 12:10
spydon added 2 commits June 12, 2026 14:20
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
spydon marked this pull request as ready for review June 12, 2026 12:47
@spydon
spydon requested a review from a team as a code owner 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.
@spydon
spydon merged commit c04d5d1 into main Jun 12, 2026
21 checks passed
@spydon
spydon deleted the lukasklingsbo/sdk-583-realtime-protocol-200 branch June 12, 2026 15:13
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 -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

realtime This issue or pull request is related to realtime

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants