Skillquality 0.46

cometchat-native-calls

CometChat Calls SDK integration for React Native (Expo managed + bare CLI). Covers @cometchat/calls-sdk-react-native install, dual-SDK init, native module linking (iOS pods, Android Gradle), VoIP push via react-native-callkeep + react-native-voip-push-notification + @react-native

Price
free
Protocol
skill
Verified
no

What it does

Purpose

Production-grade voice + video calling for React Native (Expo managed + bare CLI). Loaded by cometchat-calls when framework is expo or react-native. Operates in two modes:

  • Standalone — calls is the product. Chat SDK (signaling) + Calls SDK (WebRTC) + your own RN screens. VoIP push is mandatory — same rule as native iOS / Android.
  • Additive — calls layered onto an existing CometChat React Native UI Kit integration. Adds call buttons inline, mounts CometChatIncomingCall at app root.

Read these other skills first:

  • cometchat-calls — dispatcher (modes, hard rules, anti-patterns)
  • cometchat-native-core — Chat SDK init, login, env conventions, gesture handler peer-dep rules
  • Framework path: cometchat-native-expo-patterns (managed) OR cometchat-native-bare-patterns (CLI)

Ground truth:


1. Hard rules — RN specialization

1.0 Calls SDK login is its own step (v5+)

The v5 Calls SDK has its own auth state, separate from the Chat SDK. After CometChat.login(uid, AUTH_KEY) succeeds, you MUST also call await CometChatCalls.login(uid, AUTH_KEY) — without it, the FIRST calls API call (initiateCall, joinSession, generateToken) throws "auth token cannot be null". The chat skill's "login once, persist forever" pattern does NOT transfer.

import { CometChat } from "@cometchat/chat-sdk-react-native";
import { CometChatCalls } from "@cometchat/calls-sdk-react-native";

// ✓ RIGHT — chat login first, then calls login
await CometChat.login(uid, AUTH_KEY);
try {
  const callUser = await CometChatCalls.login(uid, AUTH_KEY);  // dev mode
  // OR for production:
  // const callUser = await CometChatCalls.loginWithAuthToken(authToken);
} catch (e) {
  // surface to user — common cause: typo in app id / auth key
}

Surprises that bite on real devices:

  • The Chat SDK persists login across launches via AsyncStorage; the Calls SDK does NOT. Even if CometChat.getLoggedinUser() returns a non-null user on cold start, call CometChatCalls.login again before any calls API works.
  • A single-arg CometChatCalls.login(uid) overload exists for re-login when the SDK has cached auth — default to the (uid, AUTH_KEY) form for dev to avoid foot-guns.
  • Login errors surface as Promise rejections — wrap in try/catch.

1.1 Dual-SDK contract

Same shape as web. @cometchat/chat-sdk-react-native initiates ringing; @cometchat/calls-sdk-react-native runs the WebRTC session. Both packages.

import { CometChat } from "@cometchat/chat-sdk-react-native";
import { CometChatCalls } from "@cometchat/calls-sdk-react-native";

// Chat SDK — initiate
const outgoing = new CometChat.Call(receiverUid, CometChat.CALL_TYPE.VIDEO, CometChat.RECEIVER_TYPE.USER);
const initiated = await CometChat.initiateCall(outgoing);

// Calls SDK — generate the token (v5 takes only sessionId; auth is internal after login).
const { token: callToken } = await CometChatCalls.generateToken(initiated.getSessionId());

// On RN, session/join is rendered via the SDK's declarative Component.
// There is NO imperative joinSession(token, settings, viewRef) on RN — the
// Component IS the call surface. With the kit, <CometChatOngoingCall />
// wraps this internally. For custom UI, render <CometChatCalls.Component
// callToken={callToken} /> directly. See references/custom-ui.md.

1.2 VoIP push — react-native-callkeep + platform-specific push

VoIP push on RN is the highest-effort piece. The standard production stack:

  • react-native-callkeep — bridges CallKit (iOS) + ConnectionService (Android). Single API for "report incoming call to OS"
  • react-native-voip-push-notification — iOS PushKit token registration + payload delivery
  • @react-native-firebase/messaging — Android FCM high-priority data messages
  • Server side — your push server must split iOS sends to PushKit (VoIP cert) and Android sends to FCM with priority: "high" and a data payload (NOT notification, which ConnectionService can't intercept)

The skill scaffolds all four pieces in standalone mode. Additive mode prompts before adding (it's substantial).

1.3 Foreground service — same Android 14+ rules as native

When the call is active on Android, an ongoing-call foreground service must run. react-native-callkeep handles registration but the app's AndroidManifest.xml must declare:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CAMERA" />
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<uses-permission android:name="android.permission.BIND_TELECOM_CONNECTION_SERVICE" />

Same silent-crash failure mode as native Android (cf. cometchat-android-v5-calls rule 1.3).

For Expo managed, these go into app.json expo.android.permissions AND require a config plugin (react-native-callkeep's plugin) to merge into the generated AndroidManifest.xml during prebuild. Bare RN can edit the manifest directly.

1.4 Server-minted auth tokens

Same — cometchat-native-production covers it.

1.5 Hangup cleanup — RTCPeerConnection + audio session

Combined web + iOS rules. RN's WebRTC bridge wraps both:

function endCall() {
  CometChatCalls.leaveSession();            // v5 canonical (endSession is a deprecated shim)
  RNCallKeep.endCall(callUUID);             // tells CallKit/ConnectionService the call ended

  // iOS: matching audio session deactivation happens inside callkeep
  // Android: foreground service stop happens inside callkeep
}

Skipping RNCallKeep.endCall leaves the system call UI stuck (lock-screen card persists, OS thinks there's an active call). Common bug.

1.6 Permissions

Required:

  • iOS Info.plistNSCameraUsageDescription, NSMicrophoneUsageDescription (same as native)
  • Android — runtime requests via PermissionsAndroid.requestMultiple for RECORD_AUDIO, CAMERA, POST_NOTIFICATIONS (Android 13+)
  • Expo managed — declare in app.json expo.ios.infoPlist and expo.android.permissions; the prebuild merges into native manifests

1.7 IncomingCall mounted at app root

<CometChatIncomingCall /> (additive mode) goes inside the root navigator OR in the App.tsx wrapper, ABOVE all stacks/tabs. Same rule as web — calls only ring on screens where the listener exists.

In standalone mode, CallKit/ConnectionService own the foreground UI; <CometChatIncomingCall /> is not used. Instead, a react-native-callkeep event listener at app root reports new calls to the OS.

1.8 Three canonical provider/scaffold patterns (non-negotiable)

Validated across 4 RN cohorts on 2026-05-14. Each bug silently breaks integration in a different way; each fix is one line. Future scaffolds MUST emit all three.

1.8.a — getLoggedInUser() throws on no-session; always .catch(() => null).

// ❌ Throws "User not found" on every fresh launch → init fails → app stuck
const existing = await CometChatUIKit.getLoggedInUser();
if (existing) return;

// ✅
const existing = await CometChatUIKit.getLoggedInUser().catch(() => null);
if (existing) return;

The RN SDK treats "no logged-in user" as a thrown error (not null), unlike the web SDK. Without the catch, every fresh launch aborts before reaching login().

1.8.b — Render e.message, not String(e).

// ❌ Most CometChat SDK errors are plain objects, not Error subclasses
// → setError(String(e)) shows "[object Object]" on screen
catch (e) {
  setError(String(e));
}

// ✅
catch (e) {
  initialized = false; // let hot-reload retry
  const msg = e instanceof Error ? e.message : JSON.stringify(e);
  setError(msg);
}

Hiding the underlying error from the user is the single biggest debugging-time-sink in this stack. Always render e.message (or JSON.stringify(e) as fallback) so the actionable text reaches the screen.

1.8.c — DO NOT pass onAccept to <CometChatIncomingCall>.

// ❌ Short-circuits the kit's internal acceptCall + OngoingCall transition.
// Symptom: callee taps Accept; caller's outgoing screen stays on "Calling…"
// indefinitely; the call connects at server level but UI never transitions.
<CometChatIncomingCall call={call} onAccept={(c) => navigate('OngoingCall', ...)} ... />

// ✅ Let the kit own the accept path; only handle decline + error
<CometChatIncomingCall
  call={call}
  onDecline={() => setCallReceived(false)}
  onError={() => setCallReceived(false)}
/>

The kit calls CometChat.acceptCall internally and pushes its own OngoingCall surface. Providing onAccept replaces that behavior with the caller's function — typically incomplete, never matches what the kit does.

Also recommended — guard creds at init time so undefined @env/process.env.EXPO_PUBLIC_* values surface as actionable errors:

if (!appId || !region) {
  throw new Error(
    `Missing CometChat credentials at init time: appId=${JSON.stringify(appId)}, region=${JSON.stringify(region)}. ` +
    `Check .env defines COMETCHAT_APP_ID/COMETCHAT_REGION/COMETCHAT_AUTH_KEY ` +
    `and restart Metro with cache wipe.`,
  );
}

Without this guard, undefined env values produce opaque TypeError: undefined is not a function from deep inside the SDK — the most expensive failure mode of a typo'd env name.


2. Setup

Bare RN CLI

npm install @cometchat/chat-sdk-react-native @cometchat/calls-sdk-react-native
npm install react-native-callkeep react-native-voip-push-notification @react-native-firebase/app @react-native-firebase/messaging
npm install react-native-webrtc           # Calls SDK peer dep
npm install react-native-gesture-handler react-native-reanimated  # already installed if using UI Kit

iOS — four hardening steps before pod install (validated 2026-05-14 on Apple Silicon, iOS 26.5 sim):

  1. USE_FRAMEWORKS=static pod install — required for WebRTC + CometChat pods. Default dynamic linkage silently produces a binary that can't load WebRTC at runtime. Set in shell rc or invoke every time:

    cd ios && USE_FRAMEWORKS=static pod install && cd ..
    # Subsequent builds:
    USE_FRAMEWORKS=static npx react-native run-ios
    
  2. Remove EXCLUDED_ARCHS arm64 i386 from ios/Podfile (Apple Silicon). RN templates often add this Intel-era workaround to the post-install hook; on Apple Silicon it blocks react-native-webrtc/JitsiWebRTC arm64 slices from linking against the simulator. Delete the line:

    # ❌ Remove this from post_install on Apple Silicon
    config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64 i386'
    
  3. ios/Info.plist — minimum set for calls:

    <key>NSCameraUsageDescription</key>
    <string>Camera access for video calls</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>Microphone access for voice and video calls</string>
    <key>NSBluetoothAlwaysUsageDescription</key>
    <string>Bluetooth access for using headsets during calls</string>
    <key>UIBackgroundModes</key>
    <array>
      <string>audio</string>
      <string>voip</string>
      <string>remote-notification</string>
    </array>
    

    UIBackgroundModes: audio is the load-bearing one for call-audio survival when the app backgrounds; without it, audio cuts the instant the app loses foreground. NSBluetoothAlways prevents a crash when the user connects a Bluetooth headset mid-call.

  4. ios/.xcode.env.local NODE_BINARY — point at a stable Homebrew Node path, not an nvm path that may have been cleaned up:

    # ios/.xcode.env.local
    export NODE_BINARY=/opt/homebrew/opt/node@20/bin/node
    

    nvm paths in ~/.nvm/versions/node/v20.x.y/bin/node go stale when nvm prunes — Xcode build fails with node: command not found. Homebrew /opt/homebrew/opt/node@20/bin/node is symlinked to whatever node@20.x is currently installed; survives brew upgrade.

iOS-on-RN works even where iOS V5 NATIVE is blocked. The native iOS V5 cohort is gated upstream by a Cloudsmith 404 on cometchat-calls-ios. The RN-on-iOS path links a different WebRTC surface (react-native-webrtc + JitsiWebRTC pod transitives), so customers on RN + iOS are NOT blocked by the native cohort's vendor issue.

Android — manifest permissions (rule 1.3 + 1.6), Firebase config (google-services.json in android/app/), service registration:

<service
  android:name="io.wazo.callkeep.RNCallKeepBackgroundMessagingService"
  android:foregroundServiceType="phoneCall|microphone|camera"
  android:exported="false" />

Expo managed

Calls SDK requires native modules — Expo managed CANNOT run it without a custom dev client. The skill detects the project mode:

  • Managed + has expo-dev-client: scaffolds config plugins for callkeep/firebase/voip-push, regenerates native projects, builds dev client
  • Managed without dev client: prompts the user — calls require either ejecting to bare or adding expo-dev-client
  • EAS Build: configures eas.json profiles + build commands

Expo Go (the public dev client) cannot run calls. The skill states this clearly and refuses to scaffold without a dev client.

⚠️ Real build-time landmines on Expo SDK 54 + chat-uikit-react-native 5.3.x (validated 2026-05-14, 4 cohorts)

A previous version of this doc listed three "Expo SDK 54 build traps" (document-picker removal, react-native-worklets install, NDK override). Re-validation on 2026-05-14 across expo-new, expo-existing, rn-new, rn-existing showed none of those three fired on the current combo (chat-uikit-react-native@5.3.5 + calls-sdk-react-native@5.0.0/4.4.1 + Expo SDK 54). Removed. The real landmines on this combo are different:

  1. @cometchat/calls-lib-webrtc is Cloudsmith-only, NOT on npm. npm install @cometchat/calls-lib-webrtc returns 404. The package lives on CometChat's Cloudsmith registry. Use the tarball URL:

    npm install --legacy-peer-deps \
      'https://dl.cloudsmith.io/public/cometchat/cometchat/raw/files/cometchat-calls-lib-webrtc-346a46ff.tgz'
    

    The exact revision hash may roll forward — check the Cloudsmith page for the current version. Without this, runtime fails when the WebRTC layer initializes.

  2. --legacy-peer-deps silently strips peer deps the kit needs at runtime. chat-uikit-react-native does not declare all its transitive runtime peers in peerDependencies (kit + calls SDK together pull in expo-linking, expo-constants, expo-asset, expo-font via expo-router, plus valibot, zustand, @xmldom/xmldom, abab, promise.allsettled, text-encoding, react-native-url-polyfill, react-native-performance). With --legacy-peer-deps, npm skips them. Each one bites on first bundle as Unable to resolve module .... Reinstall them explicitly:

    # Bare RN
    npm install --legacy-peer-deps \
      valibot zustand @xmldom/xmldom abab promise.allsettled text-encoding \
      react-native-url-polyfill react-native-performance
    
    # Expo (resolves to SDK-compatible versions)
    npx expo install \
      expo-linking expo-constants expo-asset expo-font \
      -- --legacy-peer-deps
    
  3. expo.extra (app.json) caches on the Expo dev client manifest. Edits to app.json → expo.extra after expo prebuild do NOT reload to the device — Constants.expoConfig.extra keeps reading the prebuild-time snapshot. For dev iteration on credentials, EITHER hardcode in src/config/*.ts (Metro hot-bundles source changes) OR run expo prebuild --clean && expo run:android after every app.json → extra change.

  4. react-native start does NOT run adb reverse (bare RN only). Only react-native run-android sets adb reverse tcp:8081 tcp:8081. When you restart Metro standalone (e.g. after .env changes), the device loses port-forwarding and shows "unable to load scripts." Fix:

    adb reverse tcp:8081 tcp:8081
    
  5. react-native-dotenv needs Metro --reset-cache after .env changes (bare RN only). The babel plugin processes .env at compile time, not runtime. Workflow:

    pkill -9 -f "react-native start"
    npx react-native start --reset-cache
    adb reverse tcp:8081 tcp:8081
    adb shell am force-stop com.<package> && adb shell monkey -p com.<package> -c android.intent.category.LAUNCHER 1
    

Init

// cometchat/init.ts
import { CometChat } from "@cometchat/chat-sdk-react-native";
import { CometChatCalls } from "@cometchat/calls-sdk-react-native";

let initialized = false;

export async function initCometChat() {
  if (initialized) return;

  const appSettings = new CometChat.AppSettingsBuilder()
    .subscribePresenceForAllUsers()
    .setRegion(process.env.EXPO_PUBLIC_COMETCHAT_REGION!)
    .build();

  await CometChat.init(process.env.EXPO_PUBLIC_COMETCHAT_APP_ID!, appSettings);

  const callAppSettings = new CometChatCalls.CallAppSettingsBuilder()
    .setAppId(process.env.EXPO_PUBLIC_COMETCHAT_APP_ID!)
    .setRegion(process.env.EXPO_PUBLIC_COMETCHAT_REGION!)
    .build();

  CometChatCalls.init(callAppSettings);

  initialized = true;
}

(Bare RN uses react-native-dotenv and @env imports instead of process.env.EXPO_PUBLIC_* — see cometchat-native-bare-patterns.)


3. Components catalog

Calls SDK primitives

Same names + shapes as the JavaScript SDK (Section 3 of cometchat-react-calls). The RN SDK adds platform-specific helpers — CometChatCalls.setUserVideoProxy for native track piping etc. — covered in deeper sample-app references.

UI Kit views (additive mode — @cometchat/chat-uikit-react-native)

ComponentPurpose
<CometChatCallButtons user={u} group={g} />Voice + video icon row (typically inside CometChatMessageHeader). Group + user semantics differ — see callout below
<CometChatIncomingCall />Root-mounted listener
<CometChatOutgoingCall />Auto-mounted by IncomingCall on initiate
<CometChatOngoingCall />Active call view
<CometChatCallLogs onItemClick={fn} />History

⚠️ Group calls use message-based join, not the ringing channel (validated 2026-05-15)

<CometChatCallButtons group={group} /> does NOT call CometChat.initiateCall like the 1:1 user variant does. Source: node_modules/@cometchat/chat-uikit-react-native/src/calls/CometChatCallButtons/CometChatCallButtons.tsx:138-201.

SurfaceWhat <CometChatCallButtons> does
user={u}CometChat.initiateCall(call) → standard Ringing flow → onIncomingCallReceived fires on peer's CallListener
group={g}CometChat.sendCustomMessage(meetingMessage) → meeting-card message in the group; caller jumps straight to in-call surface

Implication for receivers:

  • Other kit-based clients (apps using <CometChatMessageList /> to render group messages) — get the "Join meeting" card rendered automatically; tap to join. No additional plumbing.

  • Custom-UI clients (apps that render their own message list, or no list at all) — receive NOTHING on the CallListener channel for group calls. To handle group meetings, add a CometChat.addMessageListener and check for the custom meeting type:

    CometChat.addMessageListener('GROUP_MEETING_LISTENER', new CometChat.MessageListener({
      onCustomMessageReceived: (msg) => {
        if (msg.getCategory() === CometChat.CATEGORY_CUSTOM && msg.getType() === 'meeting') {
          const sessionId = (msg.getCustomData() as any)?.sessionId;
          const callType = (msg.getCustomData() as any)?.callType;  // "audio" | "video"
          // Show your own "incoming group call" UI; tap to navigate to ongoing-call with sessionId
        }
      },
    }));
    

This semantic is the same across all CometChat kits (React, Angular, native iOS, native Android, Flutter) — group calls broadcast via custom message, NOT the ringing channel. Document loudly because the symptom (group-call recipient sees nothing) looks like a bug but is by design.


4. Standalone integration

When product === "voice-video" and there is no existing UI Kit.

Split by calling mode — these are two different shapes:

4a. Standalone — Session mode (meeting-room UX, no ringing)

Calls SDK ONLY. NO Chat SDK. Matches ~/Downloads/calls-sdk/calls-sdk-react-native-5/sample-apps/cometchat-calls-sample-app-react-native/.

MANDATORY install set (MUST run BEFORE scaffolding files — bundle will fail with Unable to resolve module <name> for each one missing):

# Bare RN — session-only mode (all version pins are load-bearing, see notes below)
npm install --legacy-peer-deps \
  '@cometchat/calls-sdk-react-native@^5.0.0' \
  'https://dl.cloudsmith.io/public/cometchat/cometchat/raw/files/cometchat-calls-lib-webrtc-346a46ff.tgz' \
  'react-native-webrtc@^124.0.0' \
  'react-native-permissions@^5.0.0' \
  'react-native-safe-area-context@^5.0.0' \
  '@react-native-async-storage/async-storage@^2.2.0' \
  '@xmldom/xmldom@^0.8.11' \
  'react-native-svg@^15.0.0' \
  'react-native-background-timer@^2.4.1' \
  'react-native-performance@^5.1.0' \
  'react-native-url-polyfill@^2.0.0' \
  'valibot@^1.2.0' \
  'zustand@^5.0.0' \
  'text-encoding@^0.7.0' \
  'abab@^2.0.6' \
  'promise.allsettled@^1.0.7'

# The Cloudsmith tarball sometimes silently skips on the same install line as
# other packages — re-run it on its own if it's missing afterwards:
[ -d node_modules/@cometchat/calls-lib-webrtc ] || \
  npm install --save --force \
    'https://dl.cloudsmith.io/public/cometchat/cometchat/raw/files/cometchat-calls-lib-webrtc-346a46ff.tgz'

Critical version pins — empirically validated 2026-05-15 (test #3, RN bare on Pixel 3 + RN 0.85):

PinWhy
@react-native-async-storage/async-storage@^2.2.0v3.x splits Android native code into a separate Maven artifact (org.asyncstorage.shared_storage:storage-android:1.0.0) that isn't widely published. Gradle fails with Could not find org.asyncstorage.shared_storage:.... Pin to ^2.2.0 (self-contained Android).
@xmldom/xmldom@^0.8.11The Calls SDK's polyfill (dist/polyfills/browser.js) assumes the xmldom 0.8 prototype shape. v0.9.x reorganized the prototype chain; runtime crashes with Cannot set property 'innerHTML' of undefined during polyfill init. Pin to ^0.8.11 (matches SDK's declared peer range).
react-native-webrtc@^124.0.0Older versions don't support React Native 0.74+ Fabric.
valibot@^1.2.0 / zustand@^5.0.0SDK declares these specific majors; minor upgrades have been API-stable but pin to the declared range to avoid surprises.

Metro config patch: route the calls SDK to its .mjs entry. @cometchat/calls-sdk-react-native@5.0.0 ships a broken CJS bundle at dist/index.js — line 1 is import "./polyfills" (ESM syntax inside a CJS file). Metro can't parse it; the import returns undefined and crashes at runtime as Cannot read property 'CometChatCalls' of undefined in your init.ts. The dist/index.mjs is correctly formed. Patch metro.config.js:

const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const path = require('path');

const config = {
  resolver: {
    resolveRequest: (context, moduleName, platform) => {
      if (moduleName === '@cometchat/calls-sdk-react-native') {
        return {
          filePath: path.resolve(
            __dirname,
            'node_modules/@cometchat/calls-sdk-react-native/dist/index.mjs',
          ),
          type: 'sourceFile',
        };
      }
      return context.resolveRequest(context, moduleName, platform);
    },
  },
};

module.exports = mergeConfig(getDefaultConfig(__dirname), config);

After patching, npx react-native start --reset-cache to clear Metro's resolver cache, then run-android / run-ios. Customer-found 2026-05-15 (test #3).

11 peer deps total — derived empirically (test #3, 2026-05-15) by parsing the SDK's actual bundle imports (node_modules/@cometchat/calls-sdk-react-native/dist/index.js + dist/polyfills/*.js). Every one is a real bundle-time or runtime requirement. The npm CLI --legacy-peer-deps flag silently strips them, so they must be listed explicitly.

Failure-mode breakdown:

Missing peerSymptom
@react-native-async-storage/async-storageUnable to resolve module @react-native-async-storage/async-storage at first bundle
react-native-svgUnable to resolve module react-native-svg at first bundle
react-native-background-timerUnable to resolve module react-native-background-timer (polyfills/browser.js)
valibot, zustand, react-native-webrtcSame — bundle-time imports in SDK index
@xmldom/xmldom, abab, promise.allsettledSame — polyfills/browser.js DOM + Promise polyfills
text-encoding, react-native-url-polyfill, react-native-performanceRuntime — used inside SDK init code paths

Also: DO NOT install @cometchat/chat-sdk-react-native in session-only mode. Dead weight in source (never imported, never initialized) AND it transitively pulls async-storage which conflicts with our own peer-dep management.

Verify checks: rn_webrtc_peers (11-peer presence) + no_chat_sdk_in_session_only (Chat SDK absence).

The skill then scaffolds:

  1. cometchat/init.tsCometChatCalls.init({ appId, region, authKey }) ONLY. No CometChat.init, no CometChat.login. Pass authKey at init so CometChatCalls.login(uid) needs no second arg.
  2. screens/JoinSession.tsx — UID picker + Start/Join meeting + state machine (inMeeting && callToken).
  3. screens/CallRoom.tsx — Renders <CometChatCalls.Component callToken={callToken} /> inside SafeAreaView. onConnectionClosed listener resets state. See references/call-session.md.
  4. No VoIP push — session mode is link-driven, not ringing-driven. No react-native-callkeep, no PushKit, no FCM data messages.
  5. Native config — Camera + microphone permissions only.

Why no Chat SDK / no VoIP push: session mode never initiates a call entity. Customers tap a meeting link, the app generates a token, joins the session. There's nothing to "ring." Initializing both SDKs + the VoIP-push stack adds substantial complexity for zero benefit.

4b. Standalone — Ringing mode (CallKeep + CallKit/ConnectionService + Incoming/Outgoing/Ongoing kit)

Dual-SDK: Chat SDK signaling channel + Calls SDK media channel. The skill scaffolds:

  1. cometchat/init.ts — Chat SDK + Calls SDK init (sequential).
  2. cometchat/CometChatProvider.tsx — Provider with init+login gate.
  3. hooks/useCallKeep.ts — Sets up react-native-callkeep, registers event listeners (didReceiveStartCallAction, answerCall, endCall).
  4. services/voipPush.ts — Combines react-native-voip-push-notification (iOS) + @react-native-firebase/messaging (Android). Handles incoming-call payloads → RNCallKeep.displayIncomingCall(...).
  5. screens/ProfileScreen.tsx (or wherever the call trigger lives) — Voice + video buttons.
  6. screens/OngoingCallScreen.tsx — Hosts call surface via <CometChatCalls.Component /> OR custom UI overlay. Implements rule 1.5 cleanup (CometChatCalls.leaveSession() + RNCallKeep.endCall(callUUID)).
  7. screens/CallLogsScreen.tsx — Paginated history.
  8. Native config — Info.plist, AndroidManifest.xml, Firebase setup, capabilities.
  9. Server-side push docs — describes the VoIP cert + FCM key requirements; cannot automate.

5. Additive integration

When cometchat-native-core integration already exists. The skill:

  1. Adds @cometchat/calls-sdk-react-native + the four push deps.
  2. Patches cometchat/init.ts to add CometChatCalls.init after CometChat.init.
  3. Mounts <CometChatIncomingCall /> at app root (rule 1.7).
  4. Wires CometChatMessageHeader call buttons (auto-rendered when user prop is set).
  5. VoIP push: opt-in (asks user — substantial native config).
  6. Adds a CallLogsScreen to the existing navigator if the user picked "dedicated screen".

6. Anti-patterns

  1. Using Expo Go for calls. Calls require native modules that Expo Go can't load. Either eject or use a dev client. The skill refuses to scaffold against Expo Go.
  2. Skipping cd ios && pod install after install. Symbols missing → "Native module CometChatCalls is null" runtime error. Bare RN only.
  3. Mounting <CometChatIncomingCall /> inside a stack/tab navigator. Loses the listener on stack push. Mount in App.tsx above the navigator (rule 1.7).
  4. Forgetting RNCallKeep.endCall in the hangup path. CallKit/ConnectionService thinks the call is still active; lock-screen UI gets stuck.
  5. Sending Android push as notification instead of data. ConnectionService cannot intercept notification payloads. Server must send data: { type: "incoming_call", sessionId: "..." } with priority: "high".
  6. Missing Firebase google-services.json for Android. Builds fail at compile time but the error is buried in Gradle output.
  7. Mixing react-native-callkeep v4 with RN <0.70. Older RN versions need callkeep v3.x; the skill checks RN version.
  8. Passing onAccept to <CometChatIncomingCall> (rule 1.8.c). Short-circuits the kit's internal acceptCall + OngoingCall transition — callee's UI moves but caller stays on "Calling…" indefinitely. Only handle onDecline + onError; let the kit own the accept path.
  9. setError(String(e)) in catch blocks (rule 1.8.b). Most SDK errors aren't Error subclasses → screen shows [object Object]. Use e instanceof Error ? e.message : JSON.stringify(e).
  10. await CometChatUIKit.getLoggedInUser() without .catch(() => null) (rule 1.8.a). RN SDK throws "User not found" on no-session — kills init before login can run.
  11. pod install without USE_FRAMEWORKS=static. Default dynamic linkage produces a binary that can't load WebRTC at runtime. iOS only.
  12. EXCLUDED_ARCHS = 'arm64 i386' in Podfile post_install on Apple Silicon. Intel-era workaround that breaks arm64 simulator linking for WebRTC pods. Remove on Apple Silicon hosts.
  13. react-native start followed by app reload without adb reverse tcp:8081 tcp:8081. Only run-android sets the port forward; standalone Metro restart loses it. Symptom: "unable to load scripts." Bare RN only.
  14. Editing .env and expecting Metro hot-reload to pick it up. react-native-dotenv is a babel-time plugin — .env changes need Metro --reset-cache. Bare RN only.
  15. Installing CometChat deps with --legacy-peer-deps and expecting all transitive peers to land. npm silently skips peers — install valibot, zustand, @xmldom/xmldom, abab, promise.allsettled, text-encoding, react-native-url-polyfill, react-native-performance, plus Expo's expo-linking/expo-constants/expo-asset/expo-font explicitly. See §2 setup landmines.
  16. npm install @cometchat/calls-lib-webrtc. Returns 404 — package is Cloudsmith-only. Use the https://dl.cloudsmith.io/... tarball URL.

7. Verification checklist

Static:

  • All seven packages: chat-sdk-react-native, calls-sdk-react-native, callkeep, voip-push-notification, firebase/app, firebase/messaging, webrtc
  • @cometchat/calls-lib-webrtc installed via Cloudsmith tarball (not npm — npm returns 404)
  • iOS: Info.plist has UIBackgroundModes (audio + voip + remote-notification) + camera/mic/Bluetooth strings (rule 1.6 + §2 setup)
  • iOS: PushKit token registration in App.tsx (or a hook called from there)
  • iOS: USE_FRAMEWORKS=static used for pod install
  • iOS (Apple Silicon): no EXCLUDED_ARCHS = 'arm64 i386' in Podfile post_install
  • iOS: .xcode.env.local NODE_BINARY points at stable Homebrew node path
  • Android: manifest has all four FOREGROUND_SERVICE_* permissions + MANAGE_OWN_CALLS + BIND_TELECOM_CONNECTION_SERVICE
  • Android: google-services.json in android/app/
  • Android: callkeep service registered with correct foregroundServiceType
  • CometChatIncomingCall mounted at App.tsx (additive mode), no onAccept prop (rule 1.8.c)
  • CometChatUIKit.getLoggedInUser called with .catch(() => null) (rule 1.8.a)
  • Error rendering uses e.message not String(e) (rule 1.8.b)
  • Env-var guard: provider throws actionable error when appId/region undefined (rule 1.8.c "Also recommended")
  • Hangup path includes endSession + RNCallKeep.endCall
  • Module-level initialized flag

Runtime (real devices, both platforms):

  • iOS — terminated app, lock screen rings on incoming call
  • iOS — answer from lock screen → opens app, joins ongoing call
  • Android — terminated app, ConnectionService rings (full-screen heads-up) on incoming call
  • Android — answer from notification → opens app, joins ongoing call
  • Both — outgoing call connects, two-way audio + video
  • Both — hangup releases camera + mic, no lingering system call UI
  • Both — Android 14+: ongoing-call notification visible, doesn't get killed by swipe
  • Expo: only on dev client / standalone build, not Expo Go

8. Pointers

  • cometchat-native-core — provider, init, gesture handler peer deps
  • cometchat-native-{expo,bare}-patterns — pod install, gesture handler, dev client setup
  • cometchat-native-components — full UI Kit catalog (additive mode)
  • cometchat-native-push — APNs + FCM for chat (overlap with VoIP push but distinct paths — chat push is APNs/FCM standard, VoIP push is PushKit/FCM data-message)
  • cometchat-native-production — server-minted tokens
  • cometchat-native-troubleshooting — Metro cache, pod install failures, privacy manifest, gesture handler conflicts

Capabilities

skillsource-cometchatskill-cometchat-native-callstopic-agent-skillstopic-ai-agenttopic-chattopic-claude-codetopic-cometchattopic-cursortopic-messagingtopic-nextjstopic-reacttopic-react-nativetopic-ui-kit

Install

Quality

0.46/ 1.00

deterministic score 0.46 from registry signals: · indexed on github topic:agent-skills · 27 github stars · SKILL.md body (34,169 chars)

Provenance

Indexed fromgithub
Enriched2026-05-18 19:04:54Z · deterministic:skill-github:v1 · v1
First seen2026-05-18
Last seen2026-05-18

Agent access