Skip to content

fix(realtime): suppress disconnected status from onHeartbeat consumers - #2496

Merged
mandarini merged 1 commit into
masterfrom
fix/realtime-suppress-disconnected-heartbeat
Jul 6, 2026
Merged

fix(realtime): suppress disconnected status from onHeartbeat consumers#2496
mandarini merged 1 commit into
masterfrom
fix/realtime-suppress-disconnected-heartbeat

Conversation

@mandarini

@mandarini mandarini commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Description

What changed?

  • _wrapHeartbeatCallback in RealtimeClient now silently drops any heartbeat status of 'disconnected' before forwarding to the consumer callback
  • Removed disconnected from the onHeartbeat JSDoc example statuses

Why was this change needed?

onHeartbeat exposes a 'disconnected' status via HeartbeatStatus, but it fires nondeterministically. The emission comes from @supabase/phoenix's Socket.sendHeartbeat() as a race-condition safeguard: if the heartbeat timer fires after the connection closes but before clearHeartbeats() runs in onConnClose, the !isConnected() guard emits 'disconnected'. Whether a consumer's callback ever receives it depends entirely on OS scheduler timing.

Connection lifecycle signals belong to the close handler and state-change events, not the heartbeat path. Consumers relying on 'disconnected' from onHeartbeat cannot do so reliably.

Closes #2488

Screenshots/Examples

Before: consumers could receive 'disconnected' from onHeartbeat intermittently with no way to depend on it.

After: onHeartbeat only fires 'sent', 'ok', 'error', and 'timeout' -- all deterministic heartbeat lifecycle statuses. Use socket state-change events (onOpen, onClose, onError) for connection lifecycle.

Breaking changes

  • This PR contains no breaking changes

HeartbeatStatus still includes 'disconnected' in the type union -- no type surface is removed. The only observable change is that the nondeterministic emission is swallowed before reaching any consumer. A follow-up breaking change to remove 'disconnected' from HeartbeatStatus entirely is tracked for v3.

Checklist

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

Additional notes

The root-cause fix (removing the heartbeatCallback("disconnected") emission from @supabase/phoenix's sendHeartbeat()). Once that ships, the suppression guard added here can be removed as dead code.

@github-actions github-actions Bot added the realtime-js Related to the realtime-js library. label Jul 6, 2026
@mandarini mandarini self-assigned this Jul 6, 2026
@pkg-pr-new

pkg-pr-new Bot commented Jul 6, 2026

Copy link
Copy Markdown

Open in StackBlitz

@supabase/auth-js

npm i https://pkg.pr.new/@supabase/auth-js@2496

@supabase/functions-js

npm i https://pkg.pr.new/@supabase/functions-js@2496

@supabase/postgrest-js

npm i https://pkg.pr.new/@supabase/postgrest-js@2496

@supabase/realtime-js

npm i https://pkg.pr.new/@supabase/realtime-js@2496

@supabase/storage-js

npm i https://pkg.pr.new/@supabase/storage-js@2496

@supabase/supabase-js

npm i https://pkg.pr.new/@supabase/supabase-js@2496

commit: 94c0772

@coveralls

coveralls commented Jul 6, 2026

Copy link
Copy Markdown

Coverage Status

coverage: 80.309% (+0.006%) from 80.303% — fix/realtime-suppress-disconnected-heartbeat into master

@mandarini
mandarini force-pushed the fix/realtime-suppress-disconnected-heartbeat branch from 6e111df to a81ad7d Compare July 6, 2026 12:36
@mandarini
mandarini marked this pull request as ready for review July 6, 2026 13:24
@mandarini
mandarini requested review from a team as code owners July 6, 2026 13:24
@mandarini
mandarini force-pushed the fix/realtime-suppress-disconnected-heartbeat branch from a81ad7d to 94c0772 Compare July 6, 2026 13:53
@mandarini
mandarini merged commit 12214ed into master Jul 6, 2026
30 checks passed
@mandarini
mandarini deleted the fix/realtime-suppress-disconnected-heartbeat branch July 6, 2026 14:47
mandarini pushed a commit to supabase/ssr that referenced this pull request Jul 7, 2026
This PR updates `@supabase/supabase-js` to v2.110.1.

**Source**: supabase-js-stable-release

---

## Release Notes

## v2.110.1

## 2.110.1 (2026-07-07)

### 🩹 Fixes

- **auth:** defer init-time notifications until initializePromise
resolves ([#2498](supabase/supabase-js#2498))
- **realtime:** suppress disconnected status from onHeartbeat consumers
([#2496](supabase/supabase-js#2496))

### ❤️ Thank You

- Katerina Skroumpelou @mandarini

This PR was created automatically.

Co-authored-by: supabase-workflow-trigger[bot] <266661614+supabase-workflow-trigger[bot]@users.noreply.github.com>
mandarini pushed a commit to supabase/supabase that referenced this pull request Jul 7, 2026
This PR updates @supabase/*-js libraries to version 2.110.1.

**Source**: supabase-js-stable-release

**Changes**:
- Updated @supabase/supabase-js to 2.110.1
- Updated @supabase/auth-js to 2.110.1
- Updated @supabase/realtime-js to 2.110.1
- Updated @supabase/postgest-js to 2.110.1
- Refreshed pnpm-lock.yaml

---

## Release Notes

## v2.110.1

## 2.110.1 (2026-07-07)

### 🩹 Fixes

- **auth:** defer init-time notifications until initializePromise
resolves ([#2498](supabase/supabase-js#2498))
- **realtime:** suppress disconnected status from onHeartbeat consumers
([#2496](supabase/supabase-js#2496))

### ❤️ Thank You

- Katerina Skroumpelou @mandarini
## v2.110.0

## 2.110.0 (2026-06-30)

### 🚀 Features

- **repo:** drop Node.js 20 support
([#2482](supabase/supabase-js#2482))

### ❤️ Thank You

- Katerina Skroumpelou @mandarini
## v2.109.0

## 2.109.0 (2026-06-30)

### 🚀 Features

- **auth:** add custom_claims_allowlist to custom providers admin API
([#2473](supabase/supabase-js#2473))
- **realtime:** add postgres_changes filter builder, new operators and
select ([#2463](supabase/supabase-js#2463))
- **storage:** expose purgeCache for buckets and single objects
([#2429](supabase/supabase-js#2429))

### 🩹 Fixes

- **functions:** honor a caller's Content-Type override regardless of
casing ([#2455](supabase/supabase-js#2455))
- **realtime:** pin @supabase/phoenix and browser test CDN deps
([#2457](supabase/supabase-js#2457))
- **realtime:** add replication connection system message option
([#2470](supabase/supabase-js#2470))
- **storage:** keep sortBy defaults when list() is given a partial
sortBy ([#2454](supabase/supabase-js#2454))

### ❤️ Thank You

- Anubhav Anand @i-anubhav-anand
- Cemal Kılıç @cemalkilic
- Claude Opus 4.8 (1M context)
- Filipe Cabaço @filipecabaco
- Katerina Skroumpelou @mandarini
- Lenny
- Rodrigo Mansueli @mansueli

This PR was created automatically.

Co-authored-by: supabase-workflow-trigger[bot] <266661614+supabase-workflow-trigger[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

realtime-js Related to the realtime-js library.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

realtime: onHeartbeat emits disconnected inconsistently — drop it in favor of connection-close events

3 participants