Repository navigation
docs(calls/javascript): ringing with the Calls SDK 5.1 - #572
jitvarpatil wants to merge 2 commits into
Conversation
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>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 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
left a comment
There was a problem hiding this comment.
✅ 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
- Version pin is a placeholder.
@cometchat/calls-sdk-javascript@5.1.0onsetup.mdx,ringing.mdx,migrating-from-legacy-ringing.mdx. Source is5.1.0-beta.2; if this merges before public5.1.0is on npm, everynpm install/yarn addsnippet fails. Gate on the publish (or pin the beta). - "Ringing migration" dashboard step is unactionable.
migrating-from-legacy-ringing.mdxtells 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). - Status-poll endpoint auth.
ringing.mdxpollsGET /v3.0/calls/{sessionId}with the logged-in user'sauthtokenas 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 —getCallDetailsthrows). If they aren't, decline/busy/no-answer detection silently never fires.
Should-fix (follow-up acceptable)
- Multi-tab sample:
answer()anddecline()both broadcasttellOtherTabs("handled", sessionId), but the prose needs answered-elsewhere →stopRinging(…, "elsewhere")(noleave()) vs declined-elsewhere →"over"(which callsleave()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.mdxparticipant reference omitspid, whichringing.mdxand 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
left a comment
There was a problem hiding this comment.
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
endCalldoes from a prepared session. This affects Decline, Cancel and the Call status table. - Two other places where the docs and the typings disagree:
VALIDATION_ERRORfromsession.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. |
There was a problem hiding this comment.
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
rejectedandcancelledtoendCall.
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. |
There was a problem hiding this comment.
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` | |
There was a problem hiding this comment.
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 | |
There was a problem hiding this comment.
The typings don't seem to support the VALIDATION_ERROR case here:
CallSession.joinlistsVALIDATION_ERRORonly when nothing was ever prepared or joined.- The
sessionreturned byinitiateCallsays that after a failed prepare,joinstill performs a normal direct join. On a token generation failure,session.callTokenis empty and you callgenerateToken(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)}`; |
There was a problem hiding this comment.
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`. |
There was a problem hiding this comment.
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 = { |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
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
calls/javascript/ringing.mdxCometChat.initiateCall/acceptCall/rejectCall). Now:CometChatCalls.initiateCall, caller listens on the prepared session (onParticipantConnected= ringing,onParticipantJoined= answered,onConnectionClosed), callee receives throughCallListener.onIncomingCallReceivedand prepares withconnect, answers withsession.join(container), declines/cancels/busy/unanswered withendCall(sessionId, status), ring timers on both sides, multi-tab, group calls, status tablecalls/javascript/migrating-from-legacy-ringing.mdxdocs.json: why it changed, before/after diagrams, caller/callee method mapping with before/after code, checklistcalls/javascript/events.mdxonConnected,onParticipantConnected; whatonConnectionClosed/onSessionTimedOutmean on a prepared session;getActiveSession();session.addEventListener; the Leave button leaves by itselfcalls/javascript/actions.mdxendCallfor ringing calls); Session State (isSessionActive/isSessionJoined/getSessionState); Get Participantscalls/javascript/session-settings.mdxisPeerCall(existed, was undocumented on web); settings are frozen atinitiateCall/connectcalls/javascript/setup.mdxcalls/javascript/overview.mdxCallSessionin the architecture table, prepared sessions in Single Active Sessioncalls/javascript/migration-guide-v5.mdxcalls/javascript/troubleshooting.mdxsdk/javascript/calling-overview.mdx,sdk/javascript/all-real-time-listeners.mdxaddCallListener/CallListenerstay in use on the webWording follows the other platforms: "ringing" and "legacy ringing", never v1/v2.
How it was checked
cometchat-team/calls-core@prepare-session-flow(shipped as@cometchat/calls-sdk-javascript5.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 tagv5.0.5. Chat SDK behaviour (CallListenerdispatch, the caller receiving its own 1:1 call as incoming) checked inchat-sdk-javascriptWSConnectionHelper.publishCallMessage.sample-app-react/src/calls/CallProvider.tsx,call-state.ts,calls-context.ts,Home.tsxon the same ref): skip your own outgoing call inonIncomingCallReceived, declinebusyfor 1:1 only, prepare on ring withconnect, poll the call's status every 3 s on both sides, 60 s caller / 65 s callee ring timers,isPeerCallfor 1:1, group decline local only.getActiveSession()is nullable and non-null from the start of a prepare; the 5-minute prepared-session expiry reportsonConnectionClosed(prepareStatus: "failed",PREPARE_TIMEOUT), notonSessionTimedOut; the caller is not reliably told about a decline, so it polls; settings are frozen at prepare (session.join(container)takes none,joinSessionreturnsSESSION_ALREADY_PREPAREDwhile a session is prepared).#anchorin the changed pages resolves;docs.jsonparses 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 existingui-kit/reactpages 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:
isPeerCall,leave(),endCall, a replacingconnect/initiateCall, and the 5-minute expiry all go throughendConference()(calls-sdk-core/handlers/session.tsleaveSession). 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.onConnectionClosedfires andgetActiveSession()becomesnull, butprepareStatusstays"prepared". It is"failed"/PREPARE_TIMEOUTonly if the prepare had not connected yet.onParticipantJoined. The poll now treatsongoingas the answer in that case.connectrejects with two error shapes.getActiveSession()becomes non-null only onceinitiateCallhas created the call.session.sessionIdisnullafter a directjoinSession.onOutgoingCallRejected/onIncomingCallCancelleddispatch now matchesWSConnectionHelper, includingunansweredand the other-tab cases.CallSessionreplacing the 5.0 placeholder class, the newjoinSessionerrors, andleaveSession()returning a Promise.VIDEO, and end paths that did not stop each other.Before merging
@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.TODO(RC)comment (exact setting name, dashboard path, screenshot). Confirm the setting applies to web apps too.GET https://{appId}.call-{region}.cometchat.io/v3.0/calls/{sessionId}with the logged-in user'sauthtokenandappidheaders, exactly as the Connect web app does (there is no public SDK method for it:CometChatCalls.getCallDetailsthrows "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.addCallListener/CallListenerstay.connect/initiateCall. The pages document this. Whether the server honoursconference.end()from a prepared, non-moderator participant was not verified. If it does, the expiry or replace path should useforceLeaveinternally.endCall's JSDoc ("a session already in the room … no request is made") andprepare-session.test.ts(around line 1621, "in the room … makes no request") disagree with the code, which always sendsPUT /calls/:id. The docs follow the code.Could not verify from source
call-state.tsvsCallProvider.tsx). The docs treat these messages as extra signals and keep the status poll as the reliable path.GET /calls/:sessionIdresponse shape and the status vocabulary in the "Call status values" table:ongoingwhen the callee joins,ended,missed, andunansweredsent by a mobile callee's Push SDK. These are taken from Connect'scall-state.tsand the iOS/Android pages.initiatedmessage to the caller's own connections. The Chat SDK does not filter it, and the Connect app's comment says it happens.🤖 Generated with Claude Code