Skip to content

refactor(client): centralize call leave reasons and align with Swift - #2231

Open
oliverlaz wants to merge 4 commits into
mainfrom
call-leave-reasons-alignment
Open

refactor(client): centralize call leave reasons and align with Swift#2231
oliverlaz wants to merge 4 commits into
mainfrom
call-leave-reasons-alignment

Conversation

@oliverlaz

@oliverlaz oliverlaz commented May 8, 2026

Copy link
Copy Markdown
Member

💡 Overview

Route every call.leave({ message }) site in the client through a single internal CallLeaveReasons table instead of scattered string literals, and fix two places where the JS vocabulary had genuinely drifted from the Swift and Android SDKs.

The direction of this PR changed since it was opened. It originally renamed the leave strings to a new <source>:<reason> kebab-case scheme. In the meantime the Swift SDK adopted the existing JS strings verbatim in GetStream/stream-video-swift#1115 (merged 17 Apr 2026), so StreamRejectionReasonProvider.HandledCallReason now emits ring: creator rejected, ring: everyone rejected and call.ended event received exactly as this SDK does. Renaming them would break the only cross-SDK string alignment that exists and split backend analytics into two vocabularies for the same events. The table stays, the values do not change.

📝 Implementation notes

  • New internal helper packages/client/src/helpers/CallLeaveReasons.ts, 15 entries. Not re-exported from the package barrel: the leave message is an SDK-internal detail, verified absent from dist/index.d.ts.
  • Covers every leave site, including the eight the original version of this PR missed: user blocked, live ended, SFU instructed to disconnect, client.disconnectUser() called, the three giveUpAndLeave reconnect reasons and the leaveReason fallback. Both clientEventReporter.abort() reasons that were duplicating their leave message as a literal now read from the table too.
  • Fix: the accepted/rejected-elsewhere path in Call.registerEffects() called leave() with no message at all, so those leaves reported nothing. It now sends user-responded-elsewhere, matching Swift. Swift uses one value covering accepted, rejected and missed elsewhere, so the two device-specific values this branch originally proposed collapse into one. The existing StreamVideoClient.rewatch test already covered this path; its assertion is tightened to lock the string.
  • Fix: ClientEventStandardCode declared NETWORK_ERROR where Swift's ClientEventFailureCode and Android's AnalyticsCallAbortReason both use NETWORK_OFFLINE. This code is on the retry_failure_code field the backend groups on. Nothing ever emitted it, so this is a declaration-only rename with no runtime change.
  • Test assertions now pin the literal strings rather than the table constants, so an accidental value change fails CI instead of silently passing.
  • No public API change and no behavioural change beyond the two fixes above.

Full cross-SDK comparison of all 28 leave-message use cases, the failure codes and the wire enums: https://claude.ai/code/artifact/7d91310b-785b-4f0d-b4db-70498299aac9

Headline from that audit: the failure codes are a real shared contract and are in good shape after this PR. The leave messages are not a contract anywhere, and Swift is inconsistent with itself (its core layer matches JS, its SwiftUI CallViewModel emits its own blocked / unanswered / ended / auto-leave). Extending message alignment further needs a convention agreed with the iOS and Android teams rather than a unilateral rename from one platform, so this PR only preserves what already lines up.

🎫 Ticket: https://linear.app/stream/issue/XYZ-123

📑 Docs: not required, no public API surface changes

Replace ad-hoc leave-message strings with a centralized
CallLeaveReasons table under `<source>:<reason>` kebab-case
namespacing. Aligns the JS SDK with the Swift/Android reason
vocabulary so cross-platform telemetry is consistent.

- ring:* for ringing-lifecycle decisions (everyone-rejected,
  creator-rejected, timeout-creator, timeout-callee)
- event:call-ended for backend call.ended
- sfu:call-ended:<reason> built from the SFU code
- device:accepted-elsewhere / device:rejected-elsewhere for the
  multi-device path driven by Call.registerEffects()'s session
  subscription

Constants live in src/helpers/CallLeaveReasons.ts and are not
re-exported from the package barrel: the message field on leave
logs remains an internal SDK detail.

No behavior change for public API consumers.
@changeset-bot

changeset-bot Bot commented May 8, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 148e435

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

Next included review available in 49 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

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

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 74cf22d6-2fee-478d-8477-c3e51b0735a0

📥 Commits

Reviewing files that changed from the base of the PR and between 5a3dfdd and 6ab625f.

📒 Files selected for processing (9)
  • packages/client/src/Call.ts
  • packages/client/src/__tests__/Call.autodrop.test.ts
  • packages/client/src/__tests__/StreamVideoClient.rewatch.test.ts
  • packages/client/src/events/__tests__/call.test.ts
  • packages/client/src/events/call.ts
  • packages/client/src/events/internal.ts
  • packages/client/src/helpers/CallLeaveReasons.ts
  • packages/client/src/reporting/ClientEventReporter.ts
  • packages/client/src/store/stateStore.ts
📝 Walkthrough

Walkthrough

The pull request consolidates call leave and rejection messages into standardized string constants. A new CallLeaveReasons constant exports reason strings for device changes, ring timeouts, call events, and SFU callbacks. The Call class and event handlers import and apply these constants, replacing inline message literals. Tests verify the new structured reason messages.

Changes

Call Leave Reason Constants

Layer / File(s) Summary
Leave Reason Constants
packages/client/src/helpers/CallLeaveReasons.ts
New constant exports standardized reason strings: ring timeouts (ring:timeout-creator, ring:timeout-callee), rejections (ring:everyone-rejected, ring:creator-rejected), device changes (deviceAcceptedElsewhere, deviceRejectedElsewhere), event triggers (event:call-ended), and a helper sfuCallEnded(reason) formatter.
Call Timeout and Device Handling
packages/client/src/Call.ts
Import CallLeaveReasons and pass structured { message } to leave() when ringing sessions accept/reject elsewhere or auto-drop on timeout.
Event Handler Integration
packages/client/src/events/call.ts
Import CallLeaveReasons and replace inline string literals with constant messages in watchCallRejected, watchCallEnded, and watchSfuCallEnded.
Test Assertions
packages/client/src/__tests__/Call.autodrop.test.ts, packages/client/src/events/__tests__/call.test.ts
Assert that leave() is called with the exact structured reason messages (ring:timeout-creator, ring:creator-rejected, event:call-ended, sfu:call-ended:ENDED, etc.) and verify { message, reject } parameters. Add new test for creator cancellation before pickup.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Reasons crystallized into constants true,
Where timeouts and rejections now have their due,
Device changes, events, and SFU calls,
All speaking in code through standardized halls,
Tests smile bright as the messages align! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the main change: centralizing client call leave reasons and aligning them with Swift.
Description check ✅ Passed The description includes the required Overview, Implementation notes, ticket, and Docs sections. It explains the implementation, scope, fixes, testing, and API impact. The Docs section explicitly stat…
Full details: Description check

Explanation

The description includes the required Overview, Implementation notes, ticket, and Docs sections. It explains the implementation, scope, fixes, testing, and API impact. The Docs section explicitly states that documentation is not required.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch call-leave-reasons-alignment

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.

Resolves two conflicts introduced while the branch was stale:

- Call.ts ring-timeout: main reformatted the template literal, the branch
  replaced it with table constants. Kept the constants.
- events/call.ts: main added `reason: 'ended'` to the `call.ended` leave and
  wrapped both backend-ended paths in `clientEventReporter.abort()`. Kept
  main's structure with the branch's constants.

Also updates the `call.ended` test assertions for main's new `reason: 'ended'`.
The merge of main into this branch resolved Call.autodrop.test.ts by taking
the branch version wholesale, which reverted main's change passing a
ClientEventReporter into the test's Call constructor. The option is
optional, so the suite stayed green and nothing flagged it.
Route every `call.leave({ message })` site and both matching
`clientEventReporter.abort()` reasons through a single `CallLeaveReasons`
table instead of scattered string literals.

The values stay as they are on main. The Swift SDK's
`StreamRejectionReasonProvider.HandledCallReason` already emits four of them
verbatim (`ring: creator rejected`, `ring: everyone rejected`,
`call.ended event received`, `user-responded-elsewhere`), so renaming them
would split backend analytics into two vocabularies for the same events.

Two genuine misalignments fixed along the way:

- The accepted/rejected-elsewhere path called `leave()` with no message at
  all. It now sends `user-responded-elsewhere`, matching Swift. Swift uses a
  single value covering accepted, rejected and missed elsewhere, so the two
  device-specific values this branch previously proposed collapse into one.
- `ClientEventStandardCode` declared `NETWORK_ERROR` where Swift's
  `ClientEventFailureCode` and Android's `AnalyticsCallAbortReason` both use
  `NETWORK_OFFLINE`. Nothing ever emitted the code, so this is a
  declaration-only rename.
@oliverlaz oliverlaz changed the title refactor(client): standardize call.leave message strings refactor(client): centralize call leave reasons and align with Swift Sep 3, 2026
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

Bundle size

Built package output. Sizes in KB; delta vs main@5a3dfdd.

Package Unminified Minified Δ min vs main
@stream-io/video-client 791.5 KB 279.2 KB +611 B (+0.2%)
@stream-io/video-react-sdk 365.4 KB 221.9 KB 0 KB
↳ install total (+ client + react-bindings) 1189.7 KB 513.1 KB +611 B (+0.1%)
@stream-io/video-react-native-sdk 413.5 KB 196.6 KB 0 KB
↳ install total (+ client + react-bindings) 1237.9 KB 487.8 KB +611 B (+0.1%)

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.

2 participants