cometchat-react-native-v5-sdk
SkillWeb & browsingAdd voice & video calling to a React Native app FROM SCRATCH with the headless CometChat Calls SDK v5 (`@cometchat/calls-sdk-react-native@5`), no prebuilt UI Kit. init→login→generateToken→render `<CometChatCalls.Component>`, granular event listeners, in-call actions (mute/video/layout/record/raise-hand), meet-style session rooms AND 1:1 ringing (Chat SDK signaling + Calls SDK media). Triggers: 'add calling from scratch react native', 'standalone video call RN', 'headless calls sdk react native', 'build my own call screen', 'meeting room join by session id RN', 'one-on-one ringing call without uikit RN', 'expo video call'.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the cometchat-react-native-v5-sdk skill
What this skill tells your AI
The instructions your AI receives, as published by cometchat/cometchat-skills in skills/cometchat-react-native-v5-sdk/SKILL.md and read by ahel’s review.
Ground truth:
@cometchat/calls-sdk-react-native@5+ catalogrn-calls-v5.json(the closed symbol list — everyCometChatCalls.*below exists in it). Official docs:/calls/react-native/**= v5 · Docs MCP. Fetch exactsessionSettingsfields, event names and action 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-native-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-react-native@4) for call signaling — signatures fetched from docs viareferences/docs-map.md(§ "1:1 RINGING"). It does NOT depend on the React Native UI Kit. - Prefer the UI Kit instead? If the app also wants chat and a prebuilt call UI, use
cometchat-react-native-core+cometchat-react-native-calls— not this skill.
Use this skill when
"add calling from scratch / without the UI Kit", "standalone voice/video in React Native", "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-native-calls.
Prerequisites & install
npm install @cometchat/calls-sdk-react-native@5
# 6 REQUIRED peer deps — versions are pinned by the SDK; do not float them
npm install @react-native-async-storage/async-storage@^2.1.2 react-native-background-timer@^2.4.1 \
react-native-performance@^5.1.2 react-native-svg@^15.12.0 react-native-url-polyfill@2.0.0 \
react-native-webrtc@124.0.7
- 1:1 ringing additionally needs the Chat SDK for signaling:
npm install @cometchat/chat-sdk-react-native@4 - iOS —
cd ios && pod install && cd ... Add toios/<App>/Info.plist:
Xcode → target → Signing & Capabilities → Background Modes → Audio, AirPlay, and Picture in Picture (+ Voice over IP for ringing —<key>NSCameraUsageDescription</key><string>Camera access is required for video calls</string> <key>NSMicrophoneUsageDescription</key><string>Microphone access is required for voice and video calls</string>references/ringing-voip.md). - Android — add to
android/app/src/main/AndroidManifest.xmlas<uses-permission android:name="android.permission.X" />:INTERNET·CAMERA·RECORD_AUDIO·MODIFY_AUDIO_SETTINGS·ACCESS_NETWORK_STATE·BLUETOOTH(addandroid:maxSdkVersion="30") ·BLUETOOTH_CONNECT. API 23+ also requires a RUNTIME request (PermissionsAndroid.requestMultiple([CAMERA, RECORD_AUDIO])orreact-native-permissions) BEFORE starting a call. A manifest entry alone is not enough. A call that must survive backgrounding needs theFOREGROUND_SERVICE*family +WAKE_LOCK+POST_NOTIFICATIONSas well —references/ringing-voip.md§ Part 2b. - New Architecture — works on old arch, New Architecture and bridgeless with no extra setup; leave
newArchEnabledas the app needs it. The SDK ships as a legacy native module and runs through RN's interop layer, so behaviour is identical on both. - 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(@3pins the CLI major that matches the v5 skills). Mint auth tokens server-side for production; never ship the Auth Key. Rebuild the app after installing (npx react-native run-ios/run-android) — this is a native dependency, so Metro-only reload will not pick it up.
Init & login ordering (BAKED — invariant)
CometChatCalls.initFromSettings(...) (once, check the result) → CometChatCalls.login(uid, authKey) or loginWithAuthToken(token) → then generateToken / render / listeners. Nothing renders or joins before init+login resolve.
- DEFAULT to
CometChatCalls.initFromSettings(settings)— the ai-agent / telemetry-attributed path (persistsintegrationSource="ai-agent"), parallel to the chat core (RULES.md§5). It is INTENTIONALLY undocumented (ai-agent-only,@nodoc— DOCS-BACKLOG F4/C1), so theCometChatSettingsshape is baked inreferences/docs-map.md; pass it INLINE (no physicalcometchat-settings.jsonfile needed). The publicly-documentedCometChatCalls.init({ appId, region })is the FALLBACK only. - Both init entry points return a RESULT OBJECT, not a rejecting promise —
{ success: false, error: {...} } | { success: true, error: null }. A bareawait CometChatCalls.initFromSettings(s)that ignores.successsilently continues on a validation failure and the call fails later with a confusing error. Always branch on.success. - v5 authenticates itself. v4 borrowed the Chat SDK's auth token and passed it to
generateToken; v5 has its ownlogin/loginWithAuthTokenand caches the token internally, sogenerateToken(sessionId)takes no auth token. Passing one is the v4 pattern. - Coexisting with the Chat SDK / UI Kit? Log into the Chat SDK first, then share the session:
const u = await CometChat.getLoggedinUser(); if (u) await CometChatCalls.loginWithAuthToken(u.getAuthToken());. The token is a User method (getAuthToken()) — there is NO top-levelCometChat.getUserAuthToken()on the RN Chat SDK (that name is on the Calls SDK; the migration doc's v4 tab shows it wrongly — DOCS-BACKLOG). Full note:references/docs-map.md§ 1:1 RINGING.
Two build modes (BAKED — the router picks one)
- Meet-style (session room) — Calls SDK ONLY. Everyone who renders the same
sessionIdlands in the same call:generateToken(sessionId)→ render<CometChatCalls.Component>. No ringing. - 1:1 ringing — Chat SDK signals, Calls SDK carries media:
CometChat.initiateCall→ peerCallListener.onIncomingCallReceived→acceptCall/rejectCall→CometChatCalls.generateToken(call.getSessionId())→ render the Component. Fetch Chat-SDK signatures viareferences/docs-map.md§ "1:1 RINGING".⚠️ RINGING ALWAYS INCLUDES VoIP — build both halves, never ask. Signaling alone is a websocket event: it rings the callee only while their app is open, so backgrounded or killed they get nothing and the call times out (45s). Half a feature. Whenever ringing is chosen,
references/ringing-voip.mdis REQUIRED — emit the native VoIP setup in the SAME build and name the steps that are the user's (VoIP cert, Firebase, Xcode capabilities). Meet-style has no ringing and needs none of it.
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.getUserAuthToken·CometChatCalls.isUserLoggedIn·CometChatCalls.addLoginListener/removeLoginListener - Session:
CometChatCalls.generateToken·CometChatCalls.Component(the React component you RENDER to join — there is nojoinSessionon React Native) ·CometChatCalls.leaveSession·CometChatCalls.endSessionForAll - Events:
CometChatCalls.addEventListener(eventName, cb, { signal? }) → unsubscribe()(event names: FETCH the full list from/calls/react-native/events) - In-call actions — ⚠️ for CUSTOM controls ONLY; the
Componentalready 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·switchCamera·setLayout·startRecording/stopRecording/toggleRecording·startStreaming/stopStreaming·startTranscription/stopTranscription·raiseHand/lowerHand/toggleHand·pinParticipant/unpinParticipant·muteParticipant·pauseParticipantVideo·showParticipantList/hideParticipantList/toggleParticipantList·setChatButtonUnreadCount - Audio routing (RN-only):
CometChatCalls.setAudioMode·CometChatCalls.AUDIO_MODE - Picture-in-picture (RN-only):
CometChatCalls.enablePictureInPictureLayout/disablePictureInPictureLayout - Call logs: the history read is the Calls SDK
CometChatCalls.CallLogRequestBuilder(verified against installed@cometchat/calls-sdk-react-native@5, 2026-09-10) —new CometChatCalls.CallLogRequestBuilder().setLimit(n).setAuthToken(CometChatCalls.getUserAuthToken() ?? "").build()→fetchNext(): Promise<CallLog[]>— everyfetchNext()needs.setAuthToken(...)(no auto fallback; it rejects "Auth Tokenis required"). There is NOCometChat.CallLogRequestBuilderon the Chat SDK (@cometchat/chat-sdk-react-native, any version) — do not emit it.CometChatCalls.CallLogis the log model. For the transcript opt-in add.setHasTranscriptions(true)(filters to transcribed calls and attaches transcripts) — same token. Paginated history is the read — there is no working single-call-detail method (see deprecated note). - Transcription:
CometChatCalls.Transcription·CometChatCalls.TranscriptRequestBuilder·Transcript
Deprecated v5 compat surface — do NOT emit:
CallSettingsBuilder,CallAppSettingsBuilder,OngoingCallListener,startSession,endSession,switchToVideoCall,getCallDetails(a@deprecated/unsupportedvoidNO-OP on the v5 class — the functionalPromise<CallLog[]>form is only on the v4-compat class; useCallLogRequestBuilder.fetchNext()). They still run (v5 is a drop-in for v4) but are the OLD API, and v5-only events never reach anOngoingCallListener. Use plain settings +addEventListener+leaveSession. Not on React Native at all (present on web — emitting them is a hallucination):joinSession,startScreenSharing/stopScreenSharing, virtual-background methods, audio/video device enumeration (getAudioInputDevices&c.). A symbol is real iff it's inrn-calls-v5.json— confirm THERE, then fetch its signature fromreferences/docs-map.md.
Listener lifecycle (BAKED)
addEventListener RETURNS an unsubscribe function AND accepts { signal }. Prefer one AbortController per screen: pass signal to every listener and call controller.abort() in the effect cleanup — one teardown, no leaks. Register listeners BEFORE the Component mounts, and call CometChatCalls.leaveSession() on unmount. Never leak.
CometChatCallsis a SINGLETON with ONE active session. Mount exactly oneComponent. Every action (leaveSession,muteAudio,setLayout, …) takes no session id — it always targets the active session. To move to another call:leaveSession()→ awaitonSessionLeft→generateToken(next)→ re-render with the new token.
Least-code recipe (meet-style)
// STRICT-TS-CLEAN: RN exports no settings type — narrow with `as const`, or inline the object
// in the JSX prop (as below) where contextual typing narrows it. A hoisted bare literal fails tsc.
import { useEffect, useState } from "react";
import { View } from "react-native";
import { CometChatCalls } from "@cometchat/calls-sdk-react-native";
// 1. once at app start — ai-agent telemetry default; init({appId,region}) is the public-doc fallback
const res = await CometChatCalls.initFromSettings({
appId: APP_ID, region: REGION, credentials: { authKey: AUTH_KEY },
});
if (!res.success) throw new Error(res.error.message); // Result object, NOT a rejection
// 2. after the user authenticates
await CometChatCalls.login(UID, AUTH_KEY); // or loginWithAuthToken(token)
function CallScreen({ sessionId }: { sessionId: string }) {
const [callToken, setCallToken] = useState<string | null>(null);
useEffect(() => { // 3. listeners BEFORE the component mounts
const controller = new AbortController();
const { signal } = controller;
CometChatCalls.addEventListener("onSessionLeft", () => {/* pop the screen */}, { signal });
CometChatCalls.addEventListener("onParticipantJoined", (p) => {/* … */}, { signal });
return () => { controller.abort(); CometChatCalls.leaveSession(); }; // 6. one teardown
}, []);
useEffect(() => { // 4. token BEFORE render
CometChatCalls.generateToken(sessionId)
.then(({ token }) => setCallToken(token))
.catch((e) => console.error(e.errorCode, e.errorDescription));
}, [sessionId]);
if (!callToken) return null; // or a loader
return (
<View style={{ flex: 1 }}> {/* MUST be sized */}
{/* 5. JOIN = RENDER. No joinSession on React Native. */}
<CometChatCalls.Component
callToken={callToken}
sessionSettings={{ sessionType: "VIDEO", layout: "TILE" }}
/>
{/* NO control buttons here — the Component already renders mute/video/camera/leave/layout. */}
</View>
);
}
Fetch the full sessionSettings field list from /calls/react-native/session-settings. On Android, request runtime CAMERA + RECORD_AUDIO permissions before this screen mounts.
Framework notes (same SDK, per-flavour glue)
- Bare React Native — the recipe above verbatim. Rebuild natively after install; a Metro reload will not link the new native module.
- Expo — requires prebuild / dev-client; cannot run in Expo Go.
npx expo prebuild, then declare perms inapp.json(expo.ios.infoPlistNSCamera/NSMicrophone;expo.android.permissionsCAMERA/RECORD_AUDIO/MODIFY_AUDIO_SETTINGS/BLUETOOTH_CONNECT) — NOT by hand-editing Info.plist/AndroidManifest (a prebuild discards manual edits). Build withnpx expo run:ios/run:androidor EAS. - Navigation — mount the call screen as its own route. Because the SDK holds ONE session, leaving the route must
leaveSession(); do not keep a backgroundedComponentmounted on a hidden tab.
Common pitfalls (BAKED)
- Don't duplicate the built-in call controls (the #1 mistake).
<CometChatCalls.Component>renders a COMPLETE call UI — mute, camera, switch-camera, layout, recording, participant list, raise hand and the red leave button. Do NOT add your own buttons around it; they duplicate the SDK's and drift out of sync. Action methods are for CUSTOM controls ONLY (only when the user EXPLICITLY asks) — hide the built-ins first viasessionSettingshide*flags. - Reaching for
joinSession— does not exist on RN; joining IS rendering the Component.startSessionis the deprecated v4 path. - Zero-height parent — the Component fills its parent; inside a
Viewwith noflex: 1or explicit height it renders invisibly. The RN analogue of web's zero-dimension container. - Ignoring the init Result —
initFromSettings/initresolve{ success, error }rather than rejecting.awaitalone hides a validation failure. - Passing an auth token to
generateToken— the v4 pattern. In v5 youlogin()first and the SDK holds the token. - Manifest permission without the runtime request — on Android 23+ the call fails with no UI signal; there is no
getUserMediaprompt to fall back on. - Expo Go — will never work; native module. Prebuild or dev-client only.
- Mounting two Components / joining while in a session — unsupported. Leave, await
onSessionLeft, then join. - Leaked listeners / no
leaveSession— every listener must be unsubscribed (or itsAbortControlleraborted) andleaveSession()called on unmount. - Using
OngoingCallListenerfor v5 events —onSessionJoined,onConnectionLost,onLeaveSessionButtonClicked,onCallLayoutChangedand theonParticipant*family arrive ONLY viaaddEventListener; a v4 listener silently never fires for them. - Inverted flags + split toggles (v4→v5) — v4
show*builder methods became v5hide*props (showEndCallButton(true)→hideLeaveSessionButton: false; copying a v4 snippet inverts intent); andmuteAudio(true|false)split intomuteAudio()/unmuteAudio(),pauseVideo()/resumeVideo(). - Floating the peer-dep versions — install EXACTLY as written; no
^, no latest. The trap:react-native-performance6.x is published but the SDK peer is^5.1.2, and a floated^6makes every laternpm installfail withERESOLVE(surfacing long after). Mismatchedreact-native-webrtcfails at native link. Verify:npm ls react-native-performance react-native-webrtc. - Hoisting
sessionSettingswithout narrowing (strict TS — the docs' own examples fail this).sessionType/layout/audioModeare string-literal unions, so a hoisted object literal widens them tostringand failstsc(TS2322). React Native exports NO settings type —CometChatCallsis the package's ONLY named export (verified against installed@cometchat/calls-sdk-react-native@5.0.4.d.ts/.d.mts; there is no importableTranscript/CallLog/settings type), so web's "annotate asSessionSettings" trick is impossible.sessionTypeis"VOICE" | "VIDEO"— there is NO"AUDIO"value (a voice call issessionType: "VOICE"). Narrow withas const, or keep the object inline in the JSX prop. When you DO need a model type, DERIVE it from the class:type CallLog = InstanceType<typeof CometChatCalls.CallLog>, and for the audio mode use(typeof CometChatCalls.AUDIO_MODE)[keyof typeof CometChatCalls.AUDIO_MODE]:
The join-session/ringing Component-mount fences now narrow withconst sessionSettings = { sessionType: "VIDEO", layout: "TILE" } as const; // ✅ <CometChatCalls.Component callToken={token} sessionSettings={{ sessionType: "VIDEO" }} /> // ✅ inline const bad = { sessionType: "VIDEO", layout: "TILE" }; // ❌ widens to stringas const(C15 fixed there), but/calls/react-native/session-settingsstill has illustrative fences that hoist a bare literal (TS2322 on copy). Narrow every hoistedsessionSettings. - Emitting ringing WITHOUT the VoIP half. It looks complete on two open simulators and fails the first real device test. VoIP ships WITH ringing, never as a follow-up offer — see
references/ringing-voip.md. - Assuming a signature — event names,
sessionSettingsfields and action params are FETCHED from docs, never guessed.
Verify it works
- Tier-1 catalog: every
CometChatCalls.*symbol emitted appears inrn-calls-v5.json(node test-suite/scripts/verify-catalog.mjs --family rn-calls-v5). - Tier-2 fences: the emit type-checks against the installed
@cometchat/calls-sdk-react-native@5.d.ts(test-suite/typecheck/rn-v5). - Tier-3b headless smoke:
node test-suite/scripts/sdk-smoke.mjs --family rn-calls-v5 [--live|--dry]covers init→login→generateToken→listener-teardown. The media render is NOT covered —Componentneeds a real device/simulator with native WebRTC, so the node smoke proves wiring only. Verify the actual call surface on a simulator/device, and 1:1 ringing needs TWO logged-in clients (one client proves the outgoing half only). Flag "dry-mock only, not live-certified" honestly where true.
Signals
- GitHub stars
- 109
- Forks
- 2
- Last commit
- Sep 2026
ahel review
K1binfo
installs-packages
Automated review, not a security audit. Ruleset v1+k2.
Advanced
- Catalog kind
- skill
- Gateway key
cometchat-react-native-v5-sdk- Source
- github.com/cometchat/cometchat-skills