Skip to content

feat(stream): say what the connection is doing, and stop leaking sessions - #1155

Merged
teetangh merged 2 commits into
devfrom
fix/stream-connection-quality
Aug 13, 2026
Merged

feat(stream): say what the connection is doing, and stop leaking sessions#1155
teetangh merged 2 commits into
devfrom
fix/stream-connection-quality

Conversation

@teetangh

Copy link
Copy Markdown
Contributor

Replaces #1143, which failed to deploy 18 times out of 18 and was never merged. This is the half that carries the fixes; it adds no dependency and changes no package.json.

Part of #1134.

Why #1143 was split

Its deploys failed at the deploy stage, not the build — AWS rejected the server Lambda as oversized:

Failed to create function: invalid parameter for function creation:
Invalid AWS Lambda parameters used in this request.
Failed to upload file: ___netlify-server-handler

The weight is @stream-io/audio-filters-web at 44 MB, added by that PR for Krisp noise cancellation. Separately, that package is a paid Stream add-on billed per participant-minute, which is a poor fit for a pre-MVP product — and the billing shape is awkward in its own right, because Stream meters the whole call while the feature is toggled by one participant.

So the filter work is parked on parked/stream-call-filters with a tracking issue, and this PR carries everything that was actually broken.

The fix that matters most

setDisconnectionTimeout defaults to 0 in Stream, which means remain in the call until the connection restores or the call is ended. A participant who shuts their laptop therefore never emits call.session_participant_left, and the session stays open forever.

That is the origin of the 1,417 MeetingSession rows with no endedAt. Set to 90 seconds here.

The rest

Change Why
Every connection state says what it is A network blip, an SFU migration, a dead network and a permanently-failed connection all rendered the same bare spinner, and the terminal ones had no way out. Only RECONNECTING_FAILED and LEFT — the two a Call instance genuinely cannot recover from — offer a rejoin, and that rejoin goes back through /api/meetings/[id]/join because that route is what grants Stream membership.
Leave renders for every state Gating the whole button row on canRejoin left someone in RECONNECTING, MIGRATING or OFFLINE on a full-screen takeover with no control at all. A screen that occupies the whole viewport has to offer a way off it. Caught by review on #1143.
Bounded wait for the video client The resolution effect returns early while the client is undefined — correctly, since the provider mounts it lazily — but re-runs only on client or callId. A client that never arrived left isCallLoading true forever, with page.tsx rendering a skeleton with no error and no exit. Bounded at 45s, sized against the provider's own ladder (five attempts, min(1000 * 2^n, 30_000)) so it cannot fire while that is still working.
Receive-side video quality Lets a participant request less video than the sender publishes — the single largest cost lever in this subsystem.

Verification

25 suites / 333 tests pass, tsc and eslint clean. Confirmed by grep that nothing under app/, lib/ or __tests__/ references either filter package, so this branch cannot reintroduce the deploy failure.

The Netlify deploy on this PR is the real test of that claim, since the failure it replaces only ever appeared at deploy time.

…ions (#1134)

Reconstructed from #1143, which could not deploy. This half carries the fixes
and adds no dependency; noise cancellation and background blur are split out
because their packages are what broke the deploy.

**Sessions never closed because the SDK was told to wait forever.**
`setDisconnectionTimeout` defaults to 0 in Stream, which means "remain in the
call until the connection restores or the call is ended". A participant who
shuts their laptop therefore never emits `call.session_participant_left`, and
the session stays open indefinitely. That is where the 1,417 MeetingSession
rows with no `endedAt` came from. Set to 90 seconds.

**Every non-JOINED state rendered the same bare spinner.** A network blip, an
SFU migration, a dead network and a connection the SDK had permanently given
up on were indistinguishable, and the terminal ones had no way out. Each state
now says what it is, and only RECONNECTING_FAILED and LEFT — the two a Call
instance genuinely cannot recover from — offer a rejoin. That rejoin goes back
through `/api/meetings/[id]/join`, because that route is what grants Stream
membership, and restores the mic and camera to what the person had chosen
rather than the call type's defaults.

**Leave now renders for every state.** Gating the whole button row on
`canRejoin` left someone in RECONNECTING, MIGRATING or OFFLINE on a
full-screen takeover with no control at all, waiting on a reconnection that
might never come. Caught in review on #1143.

**A client that never arrives no longer means a permanent skeleton.** The
resolution effect returns early while the video client is undefined — rightly,
since the provider mounts it lazily — but it re-runs only when `client` or
`callId` changes, so a client that never came left `isCallLoading` true
forever with no error and no exit. Bounded at 45s, sized against the
provider's own retry ladder (five attempts, `min(1000 * 2^n, 30_000)`), so it
cannot fire while that is still legitimately working.

**Receive-side video quality** lets a participant ask for less video than the
sender is publishing, which is the single largest cost lever in this
subsystem.

25 suites / 333 tests, tsc and eslint clean. No package.json change.

Part of #1134
@netlify

netlify Bot commented Aug 13, 2026

Copy link
Copy Markdown

Deploy Preview for familiarise ready!

Name Link
🔨 Latest commit 5bd634f
🔍 Latest deploy log https://app.netlify.com/projects/familiarise/deploys/6a7df933df8d5100096eacfb
😎 Deploy Preview https://deploy-preview-1155--familiarise.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 38 (🔴 down 23 from production)
Accessibility: 90 (no change from production)
Best Practices: 83 (no change from production)
SEO: 82 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@teetangh, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 54 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 195b8293-5f24-4d70-b35b-d4b54155e264

📥 Commits

Reviewing files that changed from the base of the PR and between 41b0b55 and 5bd634f.

📒 Files selected for processing (11)
  • __tests__/stream/call-connection-and-quality.test.ts
  • __tests__/stream/client-wait-timeout.test.ts
  • app/meetings/[id]/components/ConnectionQualityNotice.tsx
  • app/meetings/[id]/components/ConnectionStateScreen.tsx
  • app/meetings/[id]/components/IncomingVideoQualityMenu.tsx
  • app/meetings/[id]/components/Loader.tsx
  • app/meetings/[id]/components/MeetingRoom.tsx
  • app/meetings/[id]/hooks/useGetCallById.ts
  • app/meetings/[id]/page.tsx
  • lib/stream/connection-state.ts
  • lib/stream/incoming-video.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@teetangh

Copy link
Copy Markdown
Contributor Author

Deploy green — the diagnosis is confirmed

This branch deployed successfully in 337 seconds, on the same site, same build command and same heap that failed 18 times out of 18 on #1143.

The only difference between the two is the absence of @stream-io/audio-filters-web. That settles what three exclusion attempts could not: the 44 MB package was the cause, and AWS was rejecting ___netlify-server-handler on size.

Two things follow that are worth recording.

Background blur is very likely shippable. @stream-io/video-filters-web is 30 MB, but it arrives as a transitive dependency of @stream-io/video-react-sdk — so it is already installed on dev, which deploys fine. Restoring blur costs no additional package weight. Only the Krisp audio package needs to stay out until it is worth paying for, both in Lambda size and in per-participant-minute billing.

The exclusions were not the fix and should not be mistaken for one. [functions] included_files in netlify.toml and outputFileTracingExcludes in next.config.mjs were both applied — the resolved config in the failing deploy log proves it — and the deploy still failed. They are absent from this branch entirely, and it deploys. Whoever restores the filter work should start from the fact that neither lever moved the function size, rather than assuming they were simply configured wrong.

@sonarqubecloud

Copy link
Copy Markdown

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