Ground truth:
@cometchat/calls-sdk-javascript@5+ catalogweb-calls-v5.json(the closed symbol list — everyCometChatCalls.*below exists in it). Official docs:/calls/javascript/**= v5 · Docs MCP. Fetch exactSessionSettingsfields, event names, and action-method signatures from the docs (references/docs-map.md) — never memory. APPEND to the user's app — additive wiring only (RULES.md). This is the HEADLESS path (no UI Kit); for the prebuilt drop-in call UI usecometchat-react-v7-callsinstead.
Companion skills (read first)
- Standalone/headless entry — this skill owns its own package (the Calls SDK) and has no
-coresibling; it is self-contained for meet-style calling. - For 1:1 RINGING only it also drives the Chat SDK (
@cometchat/chat-sdk-javascript@4) for call signaling — signatures fetched from docs viareferences/docs-map.md(§ "1:1 RINGING"). It does NOT depend on the React UI Kit. - Prefer the UI Kit instead? If the app also wants chat and a prebuilt call UI, use
cometchat-react-v7-core+cometchat-react-v7-calls— not this skill.
Use this skill when
"add calling from scratch / without the UI Kit", "standalone voice/video", "build my own call screen", "headless calls SDK", "meeting-room join by session id", "one-on-one ringing call". Precondition: the caller chose the from-scratch / standalone path (the "add calling" router asks this). If they want the prebuilt call UI, route to cometchat-react-v7-calls.
Prerequisites & install
bashnpm install @cometchat/calls-sdk-javascript@5
- 1:1 ringing additionally needs the Chat SDK for signaling:
bash
npm install @cometchat/chat-sdk-javascript@4 - Credentials/env: App ID · Region · Auth Key (dev only). To fetch from the dashboard, load the CLI on demand —
npx @cometchat/skills-cli@3 auth login→provision use --app-id <id> --json(writes.cometchat/config.json;@3pins the CLI major that matches the v5 skills). Or paste manually from Dashboard → Credentials. Mint auth tokens server-side for production; never ship the Auth Key. HTTPS (orlocalhost) is required for camera/mic (getUserMedia).
Init & login ordering (BAKED — invariant)
CometChatCalls.init(...) (once, resolve first) → CometChatCalls.login(uid, authKey) or loginWithAuthToken(token) → then generateToken / joinSession / listeners. Nothing renders/joins before init+login resolve.
- DEFAULT to
CometChatCalls.initFromSettings(settings)— the ai-agent / telemetry-attributed path (persistsintegrationSource="ai-agent"), parallel to how the chat core inits viaCometChatUIKit.initFromSettings/CometChat.initFromSettings(RULES §5). It is INTENTIONALLY undocumented — ai-agent-only (@nodoc, same posture asCometChatUIKit.initFromSettings— DOCS-BACKLOG F4/C1) — so the settings-object shape is baked inreferences/docs-map.md; pass it INLINE (no physicalcometchat-settings.jsonfile required), never fetched from/calls/javascript/setup. The publicly-documentedCometChatCalls.init({ appId, region })is the FALLBACK only (a non-skills / doc-following context). - Coexisting with the Chat SDK / UI Kit? Their
login()can re-init the Calls SDK with only appId/region and wipe custom hosts — re-run your calls init after their login, beforeCometChatCalls.login.
Two build modes (BAKED — the router picks one)
- Meet-style (session room) — Calls SDK ONLY. Everyone who joins the same
sessionIdlands in the same call:generateToken(sessionId)→joinSession(token, sessionSettings, containerEl). No ringing. This is the/calls/javascript/react-integrationflow. - 1:1 ringing — Chat SDK signals, Calls SDK carries media:
CometChat.initiateCall→ peerCallListener.onIncomingCallReceived→acceptCall/rejectCall→CometChatCalls.generateToken(call.getSessionId())→joinSession(...). Fetch Chat-SDK signatures viareferences/docs-map.md§ "1:1 RINGING".
SDK method map (BAKED closed list — from the catalog; signatures → FETCH from docs)
- Lifecycle:
CometChatCalls.init·CometChatCalls.initFromSettings·CometChatCalls.login·CometChatCalls.loginWithAuthToken·CometChatCalls.logout·CometChatCalls.getLoggedInUser·CometChatCalls.isUserLoggedIn - Session:
CometChatCalls.generateToken·CometChatCalls.joinSession·CometChatCalls.leaveSession(startSessionis deprecated → usejoinSession) - Events:
CometChatCalls.addEventListener(eventName, cb, { signal? }) → unsubscribe()(event names: FETCH the full list from/calls/javascript/events) - In-call actions — ⚠️ for CUSTOM controls ONLY; the default
joinSessionUI already renders all of these (see the first pitfall). Only reach for them when the user EXPLICITLY asks to replace the built-in controls:muteAudio/unmuteAudio/toggleAudio·pauseVideo/resumeVideo/toggleVideo·setLayout·startRecording/stopRecording·startScreenSharing/stopScreenSharing·raiseHand/lowerHand·switchCamera·pinParticipant/unpinParticipant·showParticipantList/hideParticipantList - Devices:
getAudioInputDevices·getAudioOutputDevices·getVideoInputDevices·getCurrent*Device - Call logs:
CometChatCalls.CallLogRequestBuilder→fetchNext()(paginated; shape → docs) - Constants:
CometChatCalls.constants.LAYOUT(TILE/SIDEBAR/SPOTLIGHT) ·.TYPE(VOICE/VIDEO) ·.CAMERA_FACING
This map is a curated highlight, NOT the full surface — the AUTHORITATIVE closed list is the
web-calls-v5.jsoncatalog (83 symbols; it also carriestoggleHand/toggleParticipantList/endSessionForAll/muteParticipant/pauseParticipantVideo/setChatButtonUnreadCount/switchToVideoCall, device setters (setAudioInputDevice…), the virtual-background methods,startStreaming/stopStreaming(RTMP live-streaming — withstreamUrl/streamKey/hideStreamingButtoninSessionSettings; neither the SKILL map above nor the/calls/javascript/actionsdoc foregrounds it, but it is real),startTranscription/stopTranscription(v5.0.5 — see/calls/javascript/transcription), andTranscriptRequestBuilder, among others). A symbol is real iff it's in the catalog — confirm THERE (not just this map), then fetch its exact signature/params from the docs page inreferences/docs-map.md.
Listener lifecycle (BAKED)
addEventListener RETURNS an unsubscribe function — collect them and call every one on teardown (React: in the effect cleanup), and CometChatCalls.leaveSession() on unmount. Register listeners BEFORE joinSession. Never leak. You can also pass an AbortSignal via the { signal } option for bulk teardown.
Least-code recipe (meet-style, framework-agnostic)
ts// STRICT-TS-CLEAN: type the settings with the EXPORTED SessionSettings, narrow region. import { CometChatCalls } from "@cometchat/calls-sdk-javascript"; import type { SessionSettings } from "@cometchat/calls-sdk-javascript"; // exported; SessionType/Layout are NOT 1. await CometChatCalls.initFromSettings({ appId, region: region as "us"|"eu"|"in", credentials: { authKey: AUTH_KEY }, callsSDK: {}, chatSDK: {}, uiKit: {} }) // ai-agent telemetry default (integrationSource="ai-agent"); init({appId,region}) is the public-doc fallback 2. await CometChatCalls.login(uid, AUTH_KEY) // or loginWithAuthToken(token) 3. const { token } = await CometChatCalls.generateToken(sessionId) 4. const unsub = CometChatCalls.addEventListener("onSessionLeft", () => cleanup()) 5. const callSettings: SessionSettings = { sessionType: "VIDEO", layout: "TILE" } // annotate → literals narrow await CometChatCalls.joinSession(token, callSettings, containerEl) 6. // NO control buttons needed — joinSession's UI ALREADY renders mute/video/screen-share/raise-hand/leave. 7. // teardown → unsub(); CometChatCalls.leaveSession()
The containerEl MUST have real dimensions — the SDK renders its call surface into it. Fetch the full SessionSettings field list from /calls/javascript/session-settings. (In plain JS drop the annotations; the doc pages show that JS form.)
Framework notes (same SDK, per-framework glue)
The recipe above is framework-agnostic; the only per-framework part is WHERE you register listeners / mount the container / tear down. Verified live on React and Angular.
- React: provider or component — init via
CometChatCalls.initFromSettings(...)(the telemetry-attributed default —integrationSource="ai-agent";init({appId,region})is the public-doc fallback), then register listeners +joinSessionin auseEffect; teardown in the effect cleanup (call every unsubscribe +leaveSession). Recipe:/calls/javascript/react-integration. - Angular: a DI service wrapping
CometChatCalls(init viaCometChatCalls.initFromSettings(...)— the telemetry-attributed default (integrationSource="ai-agent";init({appId,region})is the public-doc fallback) → login,generateToken, typedjoinSession,leaveSession; expose readiness via an RxJSBehaviorSubject) + a component with a sized@ViewChild('callContainer') ElementRefcontainer; register listeners BEFOREjoinSession; teardown inngOnDestroy(unsubscribe all +leaveSession). Recipe:/calls/javascript/angular-integration— but ⚠️ THREE things to override:- (a) its example wires external Mute/Video/Leave buttons — IGNORE them (pitfall #1:
joinSession's UI already has the controls); - (b) it targets legacy
@NgModule/app.module.ts— a modernng newis standalone (bootstrapApplication+app.config.ts, classApp), so use the standalone variant; - (c) ⚠️ NgZone / change detection (the silent dead-state). CometChat SDK callbacks — event listeners AND the
login/generateToken/joinSessionpromise resolutions — fire OUTSIDE Angular's zone, so any component state you set inside them won't trigger change detection (symptom: readiness/call buttons silently never enable, even though the SDK succeeded). Fix: drive the template off yourBehaviorSubjectvia theasyncpipe, AND/OR wrap state writes inthis.zone.run(() => …)(injectNgZone) or callChangeDetectorRef.detectChanges(). Not optional — the app looks broken without it.
- (a) its example wires external Mute/Video/Leave buttons — IGNORE them (pitfall #1:
Common pitfalls (BAKED)
- Don't duplicate the built-in call controls (the #1 mistake).
joinSessionrenders a COMPLETE call UI — mute, camera on/off, screen-share, raise-hand, participant list, layout switch, and the red leave/end button are ALL built into the surface it mounts incontainerEl. Do NOT add your own Mute / Start-video / Share-screen / Leave buttons around the container: they are redundant, duplicate the SDK's own controls, and drift out of sync with the real call state. ThemuteAudio/pauseVideo/leaveSession/… methods are for CUSTOM controls ONLY — reach for them just when the user EXPLICITLY asks to replace the default controls (and hide the built-in ones first viaSessionSettings—hideControlPanelfor the whole bar, or per-button flags likehideToggleAudioButton/hideRaiseHandButton/hideChatButton; the canonical recipe is/calls/javascript/custom-control-panel, fields on/calls/javascript/session-settings). Default = render the call surface and stop. - Zero-dimension container —
joinSessionmounts intocontainerEl; if it hasheight:0the call renders invisibly. Give it explicit size (e.g.height: 500px/100dvh). - HTTP (not HTTPS) —
getUserMedianeeds a secure context; camera/mic silently fail offlocalhost. - Joining before init+login resolve —
generateToken/joinSessionreject; alwaysawaitinit then login first. - Leaked listeners / no
leaveSession— everyaddEventListenerunsubscribe must run on teardown; callleaveSession()on unmount. startSessionis deprecated — usejoinSession; don't pass the oldCallSettingsbuilder where a plainSessionSettingsobject is expected.- Host wipe on coexisting Chat-SDK login — re-init the Calls SDK after the Chat SDK/UI Kit logs in (see Init ordering).
- version_conflict — the Calls SDK is major 5, distinct from the Chat SDK's v4; do not "upgrade" the Chat SDK to a non-existent v7.
- Assuming a signature — event names,
SessionSettingsfields, and action params are FETCHED from docs, never guessed. - 1:1 ringing:
CometChat.Callused only as a TYPE tripsTS6133— in strict TS, importingCometChatand referencingCometChat.Callin type positions only (never as a value) fails with'CometChat' is declared but its value is never read. Import the concrete type instead:import { CometChat } from "@cometchat/chat-sdk-javascript"for the VALUES you call (initiateCall/acceptCall/addCallListener) andimport type { Call } from "@cometchat/chat-sdk-javascript"for the type. (Same class as theSessionSettings/CometChatCallsstrict-TS trap.) - Strict-TS widening (the doc's bare-object form doesn't compile as-is) — the doc pages pass
joinSession(token, { sessionType: "VIDEO", layout: "TILE" }, el)andinit({ appId, region })as plain JS. In a strict-TS app (the stock Vitereact-tstemplate:strict+verbatimModuleSyntax+noUnusedLocals) those literals WIDEN tostringand failtsc(TS2345 vsSessionSettings; TS2322 forregion). Fix:import type { SessionSettings }(it's exported;SessionType/Layoutare NOT) and annotate the settings object —const s: SessionSettings = {…}(orsatisfies SessionSettings) — and narrowregionto"us"|"eu"|"in".
Verify it works
- Tier-1 catalog: every
CometChatCalls.*symbol emitted appears inweb-calls-v5.json(node test-suite/scripts/verify-catalog.mjs --family web-calls-v5). - Tier-2 fences: the emit type-checks against the installed
@cometchat/calls-sdk-javascript@5.d.ts. - Tier-3b headless smoke:
node test-suite/scripts/sdk-smoke.mjs --family web-calls-v5 [--live|--dry]runs init→login→generateToken→(join wiring)→listener-teardown. Media/joinSessionneeds a real WebRTC/DOM context, so the node smoke covers the token+listener round-trip (--dryproves wiring without a backend); the actual call render is verified in a browser harness. Flag "dry-mock only, not live-certified" honestly where true.

