Skip to content

feat(realtime): use phoenix's js lib inside realtime-js#2119

Merged
mandarini merged 74 commits intosupabase:masterfrom
software-mansion-labs:swm/realtime-use-phoenix
Mar 16, 2026
Merged

feat(realtime): use phoenix's js lib inside realtime-js#2119
mandarini merged 74 commits intosupabase:masterfrom
software-mansion-labs:swm/realtime-use-phoenix

Conversation

@GuzekAlan
Copy link
Copy Markdown
Contributor

@GuzekAlan GuzekAlan commented Feb 16, 2026

🔍 Description

Make realtime-js use phoenix js library to handle most of the logic.

What changed?

Most of the code now uses adapters to use phoenix API

Why was this change needed?

It shifts the responsibility for creating, handling, and cleaning connections and channels to phoenix js library. Only Supabase related logic for auth, and bindings filtering/formatting along exposing proper API is contained in the realtime-js

🔄 Breaking changes

  • there is no way to subscribe after unsusbcirbe - it wasn't working properly in the original implementation
  • WebSocket implementation errors will be thrown in constructor (instead of connect) -since getWebsocketConstructor is used in RealtimeClient.constructor
  • calling on with presence after subscribe will result in error - due to no resubscribe funcitonality
  • no teardown on RealtimeClient.disconnect - now channels are maintaining it's information and when socket is reconnected they keep working (this is the way phoenix does it)
  • callbacks (used in on method) can take joinRef as a 3rd parameter
  • fields which are used internally are mainly read-only

📋 Checklist

  • I have read the Contributing Guidelines
  • My PR title follows the conventional commit format: <type>(<scope>): <description>
  • I have run npx nx format to ensure consistent code formatting
  • I have added tests for new functionality (if applicable)
  • I have updated documentation (if applicable)

📝 Additional notes

In this PR some tests are removed because they are moved to https://github.com/supabase/phoenix

GuzekAlan and others added 30 commits December 10, 2025 10:37
…pabase phonix git dep (#3)

* feat(realtime): add submodule for phoenix, change deps

* fix(realtime): add temporary types since types/phoenix was removed

* feat(realtime): use phoenix consts

* fix(realtime): rename vitest extension

* fix(realtime): cannot import form path

* feat(realtime): add phoenixAdapter with basic stuff

* fix(realtime): add path to compile project

* feat(realtime): make presence use phoenixAdapter

* feat(realtime): use github path instead of git submodules

* fix(realtime): update phoenix github dependency

* fix(realtime): properly export constatns with types form phoenix

* fix(realtime): use proper types in phoenixAdapter, fix package-lock file

* fix(realtime): small changes

* fix(realtime): add and remove comments

* fix(realtime): use proper url string

* fix(realtime): better imports, fix constatns

* fix(realtime): point to branch, fix commnet place

* fix(realtime): change config file extension

* feat(realtime): update phoenix dep, remove ts-ignore

* fix(realtime): fix config extension

* fix(realtime): generic helper function in phoenixAdapter

* feat(realtime): update dep to supabase/phoenix

* fix(realtime): remove unuse function

* feat(realtime): better presence translation code, split into files

* fix(realtime): fix cloneDeep function

* fix(realtime): change privarte to internal in docs
Removed tests check behavior that is also checked in phoenix codebase.
* feat(realtime): add phoenixAdapter, refactor RealtimePresence, use supabase phonix git dep (#3)

* feat(realtime): add submodule for phoenix, change deps

* fix(realtime): add temporary types since types/phoenix was removed

* feat(realtime): use phoenix consts

* fix(realtime): rename vitest extension

* fix(realtime): cannot import form path

* feat(realtime): add phoenixAdapter with basic stuff

* fix(realtime): add path to compile project

* feat(realtime): make presence use phoenixAdapter

* feat(realtime): use github path instead of git submodules

* fix(realtime): update phoenix github dependency

* fix(realtime): properly export constatns with types form phoenix

* fix(realtime): use proper types in phoenixAdapter, fix package-lock file

* fix(realtime): small changes

* fix(realtime): add and remove comments

* fix(realtime): use proper url string

* fix(realtime): better imports, fix constatns

* fix(realtime): point to branch, fix commnet place

* fix(realtime): change config file extension

* feat(realtime): update phoenix dep, remove ts-ignore

* fix(realtime): fix config extension

* fix(realtime): generic helper function in phoenixAdapter

* feat(realtime): update dep to supabase/phoenix

* fix(realtime): remove unuse function

* feat(realtime): better presence translation code, split into files

* fix(realtime): fix cloneDeep function

* fix(realtime): change privarte to internal in docs

* feat(realtime): move PoC for RealtimeClient and RealtimeChannel

* fix(realtime): fix phoenixAdapter, make realtime work

* fix(realtime): clear code, add types, make consistent

* fix(realtime): cleaning, making supabase tests work

* fix(realtime): more cleaning and checking

* fix(realtime): import directly from pheonix, use type instead of interface

* fix(realtime): use functions directly from channelAdapter

* fix(realtime): use websocket factory

* fix(realtime): remove infered types, make trigger more private, small fixes

* fix(realtime): add ts-ignore for connectionState
* fix(realtime): fix joinedOnce

* fix(realtime): fix conn
* fix(realtime): update types from phoenix

* fix(realtime): fix type import/export

* fix(realtime): cleaner types
* fix(realtime): better error message on connect

* fix(realtime): remove unused method

* feat(realtime): make disconnect and async function to wait for the end of phoenix disconnect

* fix(realtime): remove not necessary testing stuff

* fix(realtime): fix conn in test

* fix(realtime): test websocket factory in different way

* fix(realtime): disconnect is async

* fix(realtime): better websocket server mocking

* wip

* fix(realtime): update types from phoenix

* fix(realtime): fix type import/export

* Revert "wip"

This reverts commit d2e4342.

* fix(realtime): fix lifecycle tests

* fix(realtime): remove unnecessary lines in test

* fix(realtime): cleanup after tests with testClient

* fix(realtime): post merge fix
* fix(realtime): update types from phoenix

* fix(realtime): fix type import/export

* fix(realtime): first test fixed

* fix(realtime): fix second test

* fix(realtime): add dataSpy to auth tests, better auth helpers

* fix(realtime): fix token setting and updates tests

* fix(realtime): fix som auth tests by using helpers

* fix(realtime): auth test more fixes

* fix(realtime): add reconnect test for auth

* feat(realtime): add shuffle for tests

* fix(realtime): fix sendHeartbeat

* fix(realtime): override heartbeatCallback with auth, use phoenix heartbeatCallback

* fix(realtime): small changes

* fix(realtime): heartbeat changes

* fix(realtime): better test helpers. fix auth tests

* fix(realtime): cr changes
* feat(realtime): add return values in adapters

* fix(realtime): fix bindings filter, remove return type in realtime
* fix(realtime): better setup

* fix(realtime): fix lifecycle test

* fix(realtime): fix resilience tests

* fix(realtime): cleaning in error tests

* fix(realtime): remove tests which are in phoenix
…ore/merge-master

chore(realtime): merge master
* fix(realtime): fix setup helper with passing apikey and params

* fix(realtime): fix config tests

* feat(realtime): add test for choosing correct encoder

* fix(realtime): undo removing test
* fix(realtime): fix first auth test

* fix(realtime): cleanup and add default phx join payload

* fix(realtime): fix auth resubscribe tests

* fix(realtime): refactor test to look ok
* fix(realtime): test setups

* fix(realtime): fix presence state management

* fix(realtime): fix format of onJoin and onLeave

* fix(realtime): fix message filtering tests, add new test case

* fix(realtime): add on typing, parse currentPresences when passed to triggers

* fix(realtime): fix presence helper tests

* fix(realtime): fix presence tests

* fix(realtime): import as type
* fix(realtime): realtimechannel constructor tests

* fix(realtime): fix lifecycle tests

* feat(realtime): add getCallback method

* fix(realtime): fix constant CHANNEL_STATES

* fix(realtime): better typing of encode, decode and logger

* fix(realtime):  better typing in RealtimeChannel, remove unused import, fix subscribe callback

* fix(realtime): throw error on adding presence callback after subscribe

* fix(realtime): remove unused method

* fix(realtime): example lock restored

* fix(realtime): use expect instead of assert
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/realtime-js/src/RealtimeChannel.ts`:
- Around line 352-353: The call to this.unsubscribe() in the error path is not
awaited so any rejection will be swallowed; change it to await
this.unsubscribe() inside the async handler (or append .catch(...) to log the
failure) before setting this.state = CHANNEL_STATES.errored so unsubscribe
errors are surfaced or at least logged; update the method that contains
this.unsubscribe() (the RealtimeChannel unsubscribe/error handling branch) to
either await the promise or handle rejection with a logger (e.g.,
this.unsubscribe().catch(err => this.logger.error(...))).

---

Duplicate comments:
In `@packages/core/realtime-js/src/RealtimeChannel.ts`:
- Line 175: The public field bindings on class RealtimeChannel (bindings:
Record<string, Binding[]>) is undocumented; add a concise JSDoc comment above
the bindings declaration describing its purpose, shape, and intended visibility
(e.g., "map of event names to listener bindings") or, if this is not part of the
public API, mark it as `@internal` in the JSDoc and explain why consumers should
not use it; ensure the comment mentions the Binding type and any invariants
(e.g., ownership/lifecycle) so readers understand how to interact with or avoid
the field.

In `@packages/core/realtime-js/src/RealtimeClient.ts`:
- Around line 116-181: The proxy getters on RealtimeClient (endPoint, timeout,
transport, heartbeatCallback, heartbeatIntervalMs, heartbeatTimer,
pendingHeartbeatRef, reconnectTimer, vsn, encode, decode, reconnectAfterMs,
sendBuffer, stateChangeCallbacks) are missing JSDoc visibility; if these are not
part of the public API mark each getter with `@internal` JSDoc (or add brief
public JSDoc if they should be public) so the intent is clear and the TypeDoc
output is correct; update the comment block above each getter (or a shared block
above the group) to include `@internal` and a one-line description, and ensure the
stateChangeCallbacks return-type annotation remains accurate.

In `@packages/core/realtime-js/test/RealtimeClient.resilience.test.ts`:
- Around line 6-14: The resilience tests manipulate reconnection and heartbeat
timing but currently run with real timers, which can cause flakiness; update the
test setup to enable fake timers in the beforeEach where testClient is
initialized (call the test runner's fake timer API, e.g., vi.useFakeTimers or
jest.useFakeTimers) and restore real timers in afterEach alongside
testClient.cleanup (e.g., vi.useRealTimers or jest.useRealTimers), and adjust
any timing-based assertions in tests to use the timer-control helpers
(advanceTimersByTime/advanceTimersToNextTimer) to drive reconnection/heartbeat
behavior deterministically for functions/setupRealtimeTest and the surrounding
beforeEach/afterEach.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/realtime-js/src/phoenix/presenceAdapter.ts`:
- Around line 17-37: The onJoin/onLeave handlers emit raw Phoenix metas
(newPresence['metas'] / leftPresence['metas']) while transformState normalizes
to presence_ref, causing inconsistent payload shapes; update the handlers in
presence.onJoin and presence.onLeave to map/normalize each meta to the same
shape used by transformState (replace phx_ref with presence_ref and preserve
other fields) before passing currentPresences/newPresences/leftPresences to
channel.getChannel().trigger so join/leave events match transformState output
(use parseCurrentPresences and the same normalization routine applied in
transformState).

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/core/realtime-js/src/phoenix/presenceAdapter.ts`:
- Around line 96-116: transformState currently mutates Phoenix's presence metas
by deleting phx_ref and phx_ref_prev, which corrupts Phoenix internal state used
by onLeavePayload (via parseCurrentPresences). Change transformState to return
new objects without mutation (e.g. for each meta create a new object: extract
phx_ref and phx_ref_prev and return { ...rest, presence_ref: phx_ref }) so phx_*
fields are not deleted from the original metas; ensure parseCurrentPresences
continues to call transformState and does not pass through live references.

GuzekAlan and others added 3 commits February 18, 2026 14:27
* fix(realtime): use expect.any, use fake timers

* fix(realtime): small changes

* fix(realtime): format
* fix(postgrest): enforce type safety for table and view names in from() method (supabase#2058)

* docs(auth): clarify updateUserById does not trigger client listeners (supabase#2114)

* fix(auth): resolve Firefox content script Promise.then() security errors in locks (supabase#2112)

* build(deps): bump qs from 6.14.1 to 6.14.2 in the npm_and_yarn group across 1 directory (supabase#2118)

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(release): version 2.96.0 changelogs (supabase#2121)

Co-authored-by: supabase-releaser[bot] <supabase-releaser[bot]@users.noreply.github.com>

* docs(supabase): document UNUSED_EXTERNAL_IMPORT build warning as false positive (supabase#2122)

* feat(auth): add skipAutoInitialize option to prevent constructor auto-init (supabase#2123)

* chore(release): version 2.97.0 changelogs (supabase#2124)

Co-authored-by: supabase-releaser[bot] <supabase-releaser[bot]@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Vaibhav <117663341+7ttp@users.noreply.github.com>
Co-authored-by: Katerina Skroumpelou <mandarini@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: supabase-releaser[bot] <223506987+supabase-releaser[bot]@users.noreply.github.com>
Co-authored-by: supabase-releaser[bot] <supabase-releaser[bot]@users.noreply.github.com>
@GuzekAlan GuzekAlan requested review from a team as code owners February 19, 2026 10:36
@mandarini mandarini merged commit 836385a into supabase:master Mar 16, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants