Skip to content

docs(calls/javascript): ringing with the Calls SDK 5.1 - #572

Open
jitvarpatil wants to merge 2 commits into
mainfrom
docs/javascript-ringing-calls-sdk
Open

jitvarpatil wants to merge 2 commits into
mainfrom
docs/javascript-ringing-calls-sdk

Conversation

@jitvarpatil

Copy link
Copy Markdown
Contributor

What

JavaScript (web) docs for CometChat's new ringing flow (ENG-39907), the web counterpart of the Android (#561, #569) and iOS (#570) docs. The Calls SDK 5.1 now creates, prepares, joins and ends ringing calls instead of the Chat SDK. The web has no VoIP push, so an incoming call still reaches an open tab through the Chat SDK's CometChat.addCallListener; everything after that runs on the Calls SDK.

Pages

Page Change
calls/javascript/ringing.mdx Rewritten. Was the legacy Chat SDK flow (CometChat.initiateCall / acceptCall / rejectCall). Now: CometChatCalls.initiateCall, caller listens on the prepared session (onParticipantConnected = ringing, onParticipantJoined = answered, onConnectionClosed), callee receives through CallListener.onIncomingCallReceived and prepares with connect, answers with session.join(container), declines/cancels/busy/unanswered with endCall(sessionId, status), ring timers on both sides, multi-tab, group calls, status table
calls/javascript/migrating-from-legacy-ringing.mdx New, added to the Migration Guide group in docs.json: why it changed, before/after diagrams, caller/callee method mapping with before/after code, checklist
calls/javascript/events.mdx onConnected, onParticipantConnected; what onConnectionClosed / onSessionTimedOut mean on a prepared session; getActiveSession(); session.addEventListener; the Leave button leaves by itself
calls/javascript/actions.mdx Actions on the session object; Leave Session (peer calls, prepared sessions, endCall for ringing calls); Session State (isSessionActive / isSessionJoined / getSessionState); Get Participants
calls/javascript/session-settings.mdx Documents isPeerCall (existed, was undocumented on web); settings are frozen at initiateCall / connect
calls/javascript/setup.mdx 5.1.0; ringing needs the Chat SDK on the web
calls/javascript/overview.mdx Direct vs ringing calls, Ringing card, CallSession in the architecture table, prepared sessions in Single Active Session
calls/javascript/migration-guide-v5.mdx New "5.0 to 5.1" section
calls/javascript/troubleshooting.mdx New Ringing section
sdk/javascript/calling-overview.mdx, sdk/javascript/all-real-time-listeners.mdx "Superseded" notes on the Chat SDK calling methods, stating that addCallListener / CallListener stay in use on the web

Wording follows the other platforms: "ringing" and "legacy ringing", never v1/v2.

How it was checked

  • Source of truth: cometchat-team/calls-core @ prepare-session-flow (shipped as @cometchat/calls-sdk-javascript 5.1.0-beta.2): calls-sdk-javascript/src/sdk/CometChatCalls.ts, CallSession.ts, types.ts, __tests__/prepare-session.test.ts; calls-sdk-core/handlers/session.ts, utils/session-core.ts, event-listeners/{conference-listener,prepared-remotes}.ts, store/config.ts. "New in 5.1" compared against tag v5.0.5. Chat SDK behaviour (CallListener dispatch, the caller receiving its own 1:1 call as incoming) checked in chat-sdk-javascript WSConnectionHelper.publishCallMessage.
  • Working integration: every web-specific pattern is the one the Connect web app uses (sample-app-react/src/calls/CallProvider.tsx, call-state.ts, calls-context.ts, Home.tsx on the same ref): skip your own outgoing call in onIncomingCallReceived, decline busy for 1:1 only, prepare on ring with connect, poll the call's status every 3 s on both sides, 60 s caller / 65 s callee ring timers, isPeerCall for 1:1, group decline local only.
  • Recurring mistakes from earlier rounds, checked explicitly: getActiveSession() is nullable and non-null from the start of a prepare; the 5-minute prepared-session expiry reports onConnectionClosed (prepareStatus: "failed", PREPARE_TIMEOUT), not onSessionTimedOut; the caller is not reliably told about a decline, so it polls; settings are frozen at prepare (session.join(container) takes none, joinSession returns SESSION_ALREADY_PREPARED while a session is prepared).
  • Independent review: a separate read-only pass that did not write the pages checked every API, event, value and code sample against the source; its findings were fixed (see below).
  • Links and structure: every internal link and #anchor in the changed pages resolves; docs.json parses and has the new page in navigation; code fences and <Tabs>/<Tab>/<Note>/<Warning>/<Card> tags balanced (scripted check, self-tested against a broken file). mint broken-links (Mintlify CLI, Node 20): no broken links; every changed page parses (the only unparsable files are 5 existing ui-kit/react pages not touched here).

What the independent review changed

Two rounds. Round 1 raised 16 findings and round 2 re-checked the fixes. All are fixed except the version pin, which is a placeholder on purpose. The main ones:

  • Leaving a prepared 1:1 session ends the room for everyone in it. With isPeerCall, leave(), endCall, a replacing connect / initiateCall, and the 5-minute expiry all go through endConference() (calls-sdk-core/handlers/session.ts leaveSession). The pages now say so in a Warning. The callee sample no longer leaves a prepared session for a call answered in another tab or on another device. The earlier sample had a race that could hang up a call the user had just answered.
  • The 5-minute expiry: onConnectionClosed fires and getActiveSession() becomes null, but prepareStatus stays "prepared". It is "failed" / PREPARE_TIMEOUT only if the prepare had not connected yet.
  • A caller whose own prepare failed is not in the room, so it never gets onParticipantJoined. The poll now treats ongoing as the answer in that case.
  • Other fixes:
    • connect rejects with two error shapes.
    • getActiveSession() becomes non-null only once initiateCall has created the call.
    • session.sessionId is null after a direct joinSession.
    • onOutgoingCallRejected / onIncomingCallCancelled dispatch now matches WSConnectionHelper, including unanswered and the other-tab cases.
    • The 5.0 → 5.1 "Changed" list covers CallSession replacing the 5.0 placeholder class, the new joinSession errors, and leaveSession() returning a Promise.
    • Sample bugs: undeclared variables, hard-coded VIDEO, and end paths that did not stop each other.

Before merging

  • Versions are placeholders: @cometchat/calls-sdk-javascript@5.1.0 (setup, ringing, migration pages). Source is 5.1.0-beta.2; update when the public 5.1.0 is published.
  • Ringing migration dashboard setting: the migration page copies the Android/iOS "Before you start" step and its TODO(RC) comment (exact setting name, dashboard path, screenshot). Confirm the setting applies to web apps too.
  • Status polling endpoint: the poll calls GET https://{appId}.call-{region}.cometchat.io/v3.0/calls/{sessionId} with the logged-in user's authtoken and appid headers, exactly as the Connect web app does (there is no public SDK method for it: CometChatCalls.getCallDetails throws "not supported"). The public REST docs show this endpoint with a REST API key only. Confirm user auth tokens are a supported way to call it, or add an SDK method.
  • Chat SDK deprecation: the pages say the Chat SDK calling methods are superseded, without promising a deprecation release (iOS/Android pages say "a coming Chat SDK release marks them deprecated"). Confirm the JS Chat SDK plan, and that addCallListener / CallListener stay.
  • UI Kit: the migration page tells UI Kit users to keep legacy ringing until a coming UI Kit release, as iOS does. Confirm for the React UI Kit.
  • SDK gap, for the Calls SDK team: there is no local-only leave for a prepared 1:1 session. A tab that prepared a call which was then answered elsewhere still ends the room when its prepared session expires (5 min) or is replaced by its next connect / initiateCall. The pages document this. Whether the server honours conference.end() from a prepared, non-moderator participant was not verified. If it does, the expiry or replace path should use forceLeave internally.
  • SDK test vs code: endCall's JSDoc ("a session already in the room … no request is made") and prepare-session.test.ts (around line 1621, "in the room … makes no request") disagree with the code, which always sends PUT /calls/:id. The docs follow the code.

Could not verify from source

  • Whether a decline, busy or cancel always produces a Chat SDK call message to the other party. The Connect app's own comments disagree (call-state.ts vs CallProvider.tsx). The docs treat these messages as extra signals and keep the status poll as the reliable path.
  • The GET /calls/:sessionId response shape and the status vocabulary in the "Call status values" table: ongoing when the callee joins, ended, missed, and unanswered sent by a mobile callee's Push SDK. These are taken from Connect's call-state.ts and the iOS/Android pages.
  • That the backend echoes a 1:1 initiated message to the caller's own connections. The Chat SDK does not filter it, and the Connect app's comment says it happens.
  • Web ↔ mobile interop and the mobile ring window. These were not exercised here.
  • The pages were not viewed in a rendered Mintlify preview. Only the CLI link check and the scripted structure check were run.

🤖 Generated with Claude Code

Rewrite the JavaScript Ringing page for the new ringing flow (ENG-39907):
the Calls SDK creates, prepares, joins and ends calls, and an incoming
call still reaches an open tab through the Chat SDK's CallListener (the
web has no VoIP push). Covers the caller listening on the prepared
session, the callee preparing with connect(), decline/cancel/busy/
unanswered with endCall(), the status poll for declines the caller is
not told about, ring timers, multiple tabs and group calls.

Add Migrating from Legacy Ringing, and update setup, overview, events,
actions, session settings (isPeerCall), the v5 migration guide (5.0 to
5.1), troubleshooting, and the Chat SDK calling pages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mintlify

mintlify Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
cometchat 🟢 Ready View Preview Oct 20, 2026, 11:00 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

A callee on a phone rings for 60 s, so the caller gives up first at 45 s and the
callee never rings for a call nobody is waiting on. Matches the iOS, Android and
web ringing samples. The web callee stops ringing after 60 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@raj-dubey1 raj-dubey1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Approved — with 3 confirm-before-merge items

Reviewed end-to-end across two independent passes: docs mechanics (redirects / nav / link-rot) and verification of every documented API, event, error code, status value and code sample against the Calls SDK 5.1 source (cometchat-team/calls-core@prepare-session-flow = @cometchat/calls-sdk-javascript 5.1.0-beta.2).

This is an unusually accurate docs PR. The three items below are all already in your own "Before merging" checklist — none is a prose/code defect — but they do gate the merge.

Confirm before merge

  1. Version pin is a placeholder. @cometchat/calls-sdk-javascript@5.1.0 on setup.mdx, ringing.mdx, migrating-from-legacy-ringing.mdx. Source is 5.1.0-beta.2; if this merges before public 5.1.0 is on npm, every npm install / yarn add snippet fails. Gate on the publish (or pin the beta).
  2. "Ringing migration" dashboard step is unactionable. migrating-from-legacy-ringing.mdx tells readers to enable a dashboard setting, but its name/path/screenshot is a hidden {/* TODO(RC) */} MDX comment (line 18) — invisible when rendered. It's a gating step 0 a migrating reader can't complete. Fill in the setting name + path (or confirm it doesn't apply to web and drop the step).
  3. Status-poll endpoint auth. ringing.mdx polls GET /v3.0/calls/{sessionId} with the logged-in user's authtoken as the reliable decline/busy/no-answer path. The public REST docs show that endpoint with a REST API key; please confirm user auth tokens are accepted (there is no SDK method — getCallDetails throws). If they aren't, decline/busy/no-answer detection silently never fires.

Should-fix (follow-up acceptable)

  • Multi-tab sample: answer() and decline() both broadcast tellOtherTabs("handled", sessionId), but the prose needs answered-elsewhere → stopRinging(…, "elsewhere") (no leave()) vs declined-elsewhere → "over" (which calls leave() and, on a prepared 1:1 session, ends the room). Send distinct tags, or state that a received "handled" must be treated as "elsewhere" only — otherwise a reader can hang up a call answered in another tab.
  • Ring timers (45 s caller / 60 s callee): internally consistent, but diverge from the Connect app (60/65) and the "phone rings 60 s" rationale is in the PR's own "could not verify" list. Align with the shipped app or mark the numbers illustrative.
  • "Superseded, not deprecated" wording + UI Kit guidance: consistent and well-written, but assert a product plan only the Chat-SDK / UI-Kit owners can confirm.

Nit

  • events.mdx participant reference omits pid, which ringing.mdx and the SDK's participant schema include.

Verified clean

docs.json valid JSON · exactly 1 nav entry added (the new page, which exists) · 0 removed/renamed → no redirects needed · all internal links + anchors across the 12 changed pages resolve · all MDX tags and code fences balanced · versions internally consistent · and the documented 5.1 API surface (signatures, ~50 event names, PrepareStatus/EndCallStatus, the 5-minute-expiry / PREPARE_TIMEOUT semantics, and the "prepared 1:1 leave ends the room" / no-local-only-leave behavior) all match source.

Approving so this isn't blocked on the review — please just close out items 1–3 before merging.

@PrajwalDhuleCC PrajwalDhuleCC left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I tested the JavaScript ringing flow against 5.1.0-beta.2 with the React ringing sample: three users in the browser, 1:1 voice and video, decline, cancel, no answer after 45 s, busy, group calls and several tabs. Most of the flow matches these docs.

Notes inline:

  • Likely contradiction with the SDK typings: what endCall does from a prepared session. This affects Decline, Cancel and the Call status table.
  • Two other places where the docs and the typings disagree: VALIDATION_ERROR from session.join, and which events fire on a prepared session.
  • A question about the call status endpoint, and a question about the multi-tab limitation.
  • A few nits.

Not tested: camera and microphone in a real browser, and web ↔ mobile.


## Decline a call

Decline a 1:1 call with `endCall` and the `rejected` status. When this tab had prepared the session, `endCall` first starts tearing it down (in a 1:1 call that asks the server to end the room), then tells the server the status. `onConnectionClosed` follows when the room closes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The 5.1.0-beta.2 typings for endCall describe this differently. A session that is already in the room tells the other participants through a cc-calls-internal-event instead, "and no request is made". The request is only a fallback if that send fails. That contradicts:

  • "then tells the server the status" here;
  • "resolves … once the server has the new status" (L485);
  • the same wording for Cancel (L489);
  • the Call status table (L570–579), which attributes rejected and cancelled to endCall.

What I saw with the React sample: the callee declines after the caller shows "Ringing…". The caller's onConnectionClosed fires, and a status read about 1.5 s later doesn't return rejected yet, so the caller shows a generic "Call ended". This happened in 2 of 2 tries. The call log does read Declined later, so the status gets recorded, just not by the time endCall resolves.

Could we confirm with the SDK team which is right? If the typings are, it would help to say here that the status can lag behind onConnectionClosed. A caller should then keep reading it for a few seconds before falling back to a generic "ended".

<Warning>
**Leaving a prepared 1:1 session ends the room for everyone in it.** With `isPeerCall: true`, tearing down a prepared session that is in the room, whether by `leave()`, `endCall`, a `connect` or `initiateCall` for another session, or the 5-minute expiry, asks the server to end the room, not only to remove this tab. That is how a cancel or decline reaches the other side, and it does no harm once the ring is over.

It is a problem when the call was answered in another tab or on another device: that tab's prepared session is still in the room of the live call. Do not call `leave()` on it. That only delays the problem: the 5-minute expiry, or the next `connect` or `initiateCall` in that tab, tears the session down the same way. The Calls SDK has no local-only leave for a prepared 1:1 session, so there is no client-side way around this.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I reproduced the first half with the React sample on beta.2. With two tabs open, a tab that calls leave() on its prepared session after another tab answered ends the call about 1 s after the answer. Stopping without leave() fixes it, as documented here.

But as this paragraph says, the idle tab's next connect or initiateCall, or the 5-minute expiry, still ends the live call. So a user with several tabs can drop an answered call just by starting another call from a different tab. Is a local-only release planned for 5.1.0? If not, this may deserve its own known-limitation callout, not only a mention inside this Warning (and troubleshooting L91).

| Event | On the prepared session it means |
|-------|----------------------------------|
| `onConnected` | Your own prepare finished; the join will be near-instant |
| `onParticipantConnected(participant)` | The callee's device prepared the session: it is ringing there. `participant` carries `uid`, `name`, `pid` and `avatar` |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Confirmed in testing on beta.2: onParticipantConnected and onParticipantJoined do fire on the caller's prepared session, so this table is right.

FYI, the connect JSDoc in the typings says otherwise: while prepared, "only terminal events can fire" (onConnectionFailed, onConnectionClosed). That contradicts this table and the paragraph in events.mdx. The typings are probably what needs updating, but please flag it to the SDK team so the two agree at release.

| State of the session | What `session.join(container)` does |
|----------------------|--------------------------------------|
| Prepared, or still preparing | The actual join, near-instant. `onSessionJoined` fires, then the camera and microphone open |
| The prepare failed or expired, and nothing is live | A normal direct join with the settings frozen at prepare. If no call token was generated (`TOKEN_GENERATION_FAILED`, or none came back with `initiateCall`), it resolves with `VALIDATION_ERROR`: use `generateToken` and `joinSession` instead |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The typings don't seem to support the VALIDATION_ERROR case here:

  • CallSession.join lists VALIDATION_ERROR only when nothing was ever prepared or joined.
  • The session returned by initiateCall says that after a failed prepare, join still performs a normal direct join. On a token generation failure, session.callToken is empty and you call generateToken(sessionId) yourself.

The answer flow at L423–424 branches on this error, so it's worth confirming what join actually resolves with when no token is available.

async function fetchCallStatus(sessionId) {
const authToken = CometChatCalls.getUserAuthToken();
if (!authToken) return null;
const url = `https://${APP_ID}.call-${REGION}.cometchat.io/v3.0/calls/${encodeURIComponent(sessionId)}`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Question: is GET /v3.0/calls/:sessionId on the call-<region> host a supported public endpoint? The SDK doesn't wrap it, and every integrator would build this request by hand with the auth token. If it is public, a link to its reference would help. If not, could the SDK expose it before we document it, for example as getCallStatus(sessionId) or a session ID filter on CallLogRequestBuilder?

- Tell your other tabs, for example with a [`BroadcastChannel`](https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel) (`tellOtherTabs` in the samples above). A tab that hears another tab answered calls `stopRinging(sessionId, "elsewhere")`, which does not call `leave()` on its prepared session, because releasing a prepared 1:1 session ends the room. The session still expires after 5 minutes, with the same effect (see the warning under [Prepare the ringing call](#prepare-the-ringing-call)). A tab that hears another tab declined calls `stopRinging(sessionId, "over")`.
- A 1:1 decline also reaches your other tabs as `onOutgoingCallRejected`, as shown in [Receive a call](#receive-a-call).
- The status poll is the backstop: a call answered on another of the user's devices reads `ongoing`, which stops a 1:1 ring as `"elsewhere"`.
- While one tab is in a call, the Connect web app keeps the other tabs from ringing; the tab in the call declines a 1:1 call with `busy`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: "the Connect web app" reads like an internal product name. Did you mean "your app" or "the sample"? This bullet also describes something the app itself has to do: track which tab is in a call over the BroadcastChannel and skip ringing in the others. It may read better written as guidance.

const appId = "APP_ID";
const region = "REGION";
const isGroup = false; // true to ring a group (see Group calls)
const sessionSettings = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: this is fine as JavaScript. Pasted into TypeScript, though, it fails with TS2322 on sessionType: the object is widened to string before it reaches initiateCall, and string isn't a SessionType. Inlining the object into the call, or a short note about as const or a SessionSettings type, would help TS readers.

### Leave Session

Ends your participation and disconnects gracefully. The call continues for other participants.
Ends your participation and disconnects gracefully. In a 1:1 call this is the hang-up: the other side receives `onParticipantLeft`, and a session joined as a peer call (`isPeerCall: true`) ends for both. In a group call the session continues for the other participants. Leaving also cancels a session that is only prepared: `onConnectionClosed` fires instead of `onSessionLeft`. A prepared peer session is already in the room, so leaving it ends the room for everyone in it, as a hang-up would.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: the isPeerCall typings add an exception to "ends for both": the session ends for both unless forceLeave is passed when leaving. It may be worth mentioning here or in Session Settings.

This branch was successfully deployed

1 active deployment
staging — 7171e06f Deployed Oct 20, 2026 by mintlify[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants