diff --git a/calls/v4/react-native/overview.mdx b/calls/v4/react-native/overview.mdx
index 5d8af9ad8..b4b75182c 100644
--- a/calls/v4/react-native/overview.mdx
+++ b/calls/v4/react-native/overview.mdx
@@ -105,7 +105,7 @@ Use this when you want:
- **Calls not connecting:** Verify the Calls SDK is initialized after the Chat SDK and that both use the same App ID and Region
- **No audio/video:** Check that camera and microphone permissions are granted on both Android and iOS
- - **Push notifications not arriving:** Ensure push notification setup is complete — see the [Push Notifications](/notifications/react-native-push-notifications-android) guide
+ - **Push notifications not arriving:** Ensure push notification setup is complete — see the [Push Notifications](/notifications/react-native-push-notifications) guide
- **iOS build fails:** Run `pod install` in the `ios` directory after adding the Calls SDK dependency
- **Android minSdkVersion error:** Set `minSdkVersion` to 24 or higher in your `build.gradle`
diff --git a/docs.json b/docs.json
index 86152f233..c38c351a4 100644
--- a/docs.json
+++ b/docs.json
@@ -6515,8 +6515,7 @@
"notifications/ios-fcm-push-notifications",
"notifications/flutter-push-notifications-android",
"notifications/flutter-push-notifications-ios",
- "notifications/react-native-push-notifications-android",
- "notifications/react-native-push-notifications-ios",
+ "notifications/react-native-push-notifications",
"notifications/web-push-notifications"
]
},
@@ -7135,7 +7134,7 @@
},
{
"source": "/extensions/react-native-push-notifications",
- "destination": "/notifications/react-native-push-notifications-android"
+ "destination": "/notifications/react-native-push-notifications"
},
{
"source": "/extensions/capacitor-cordova-ionic-push-notifications",
@@ -7430,8 +7429,12 @@
"destination": "https://assets.cometchat.io/legacy-docs/notifications/push-notification-extension-legacy.html"
},
{
- "source": "/notifications/react-native-push-notifications",
- "destination": "/notifications/react-native-push-notifications-android"
+ "source": "/notifications/react-native-push-notifications-android",
+ "destination": "/notifications/react-native-push-notifications"
+ },
+ {
+ "source": "/notifications/react-native-push-notifications-ios",
+ "destination": "/notifications/react-native-push-notifications"
},
{
"source": "/rest-api/messages/list-reactions-with-a-specific-emojiunicodes",
diff --git a/notifications.mdx b/notifications.mdx
index 0eb31ff02..e3ad91797 100644
--- a/notifications.mdx
+++ b/notifications.mdx
@@ -66,8 +66,7 @@ canonical: "https://cometchat.com/docs"
} href="/notifications/flutter-push-notifications-android" horizontal />
} href="/notifications/flutter-push-notifications-ios" horizontal />
- } href="/notifications/react-native-push-notifications-android" horizontal />
- } href="/notifications/react-native-push-notifications-ios" horizontal />
+ } href="/notifications/react-native-push-notifications" horizontal />
} href="/notifications/web-push-notifications" horizontal />
diff --git a/notifications/push-overview.mdx b/notifications/push-overview.mdx
index 6d83a545f..f8fb45c27 100644
--- a/notifications/push-overview.mdx
+++ b/notifications/push-overview.mdx
@@ -63,11 +63,7 @@ UI Kit implementation
UI Kit implementation
-} href="/notifications/react-native-push-notifications-android">
-UI Kit implementation
-
-
-} href="/notifications/react-native-push-notifications-ios">
+} href="/notifications/react-native-push-notifications">
UI Kit implementation
diff --git a/notifications/react-native-push-notifications-android.mdx b/notifications/react-native-push-notifications-android.mdx
deleted file mode 100644
index a51e7a886..000000000
--- a/notifications/react-native-push-notifications-android.mdx
+++ /dev/null
@@ -1,959 +0,0 @@
----
-title: "React Native Push Notification (Android)"
-description: "Bring the SampleAppWithPushNotifications experience—FCM + VoIP calls—into any React Native project using CometChat UI Kit."
----
-
-
-
-| Field | Value |
-| --- | --- |
-| Platform | Android (FCM) |
-| Key Classes | `CometChatNotifications`, `VoipNotificationHandler`, `PendingCallManager` |
-| Key Methods | `registerPushToken()`, `unregisterPushToken()`, `messaging().getToken()` |
-| Push Platform | `CometChatNotifications.PushPlatforms.FCM_REACT_NATIVE_ANDROID` |
-| Prerequisites | CometChat SDK initialized, user logged in, FCM configured, `google-services.json` in `android/app` |
-
-
-
-
- Reference implementation of React Native UI Kit, FCM and Push Notification Setup.
-
-
-## What this guide covers
-
-- CometChat Dashboard setup (enable push, add FCM providers).
-- Platform credentials (Firebase).
-- Copying the sample notification stack and aligning IDs/provider IDs.
-- Native glue for Android (manifest permissions).
-- VoIP call alerts with FCM data-only pushes + CallKeep native dialer.
-- Token registration, navigation from pushes, testing, and troubleshooting.
-
-## What you need first
-
-- CometChat app credentials (App ID, Region, Auth Key) and Push Notifications enabled with an **FCM provider (React Native Android)**.
-- Firebase project with an Android app (`google-services.json` in `android/app`) and Cloud Messaging enabled.
-- React Native 0.81+, Node 18+, physical Android devices for reliable push/call testing.
-
-## How FCM + CometChat work together
-
-- **FCM (Android) is the transport:** Firebase issues the Android FCM token and delivers payloads to devices.
-- **CometChat provider holds your credentials:** The FCM provider you add (for React Native Android) stores your Firebase service account JSON.
-- **Registration flow:** Request permission → Android returns the FCM token → after `CometChat.login`, register with `CometChatNotifications.registerPushToken(token, platform, providerId)` using `FCM_REACT_NATIVE_ANDROID` → CometChat sends pushes to FCM on your behalf → the app handles taps/foreground events via Notifee.
-
-## 1. Enable push and add providers (CometChat Dashboard)
-
-1. Go to **Notifications → Settings** and enable **Push Notifications**.
-
-
-
-
-
-2. Add an **FCM** provider for React Native Android; upload the Firebase service account JSON and copy the Provider ID.
-
-
-
-
-
-## 2. Prepare platform credentials
-
-### 2.1 Firebase Console
-
-1. Register your Android package name (same as `applicationId` in `android/app/build.gradle`) and download `google-services.json` into `android/app`.
-2. Enable Cloud Messaging.
-
-
-
-
-
-## 3. Local configuration
-
-- Update `src/utils/AppConstants.tsx` with `appId`, `authKey`, `region`, and `fcmProviderId`.
-- Keep `app.json` name consistent with your bundle ID / applicationId.
-
-```ts lines
-const APP_ID = "";
-const AUTH_KEY = "";
-const REGION = "";
-const DEMO_UID = "cometchat-uid-1";
-```
-
-### 3.1 Dependencies snapshot (from Sample App)
-
-Install these dependencies in your React Native app:
-
-```npm lines
-npm install \
- @react-native-firebase/app@23.4.0 \
- @react-native-firebase/messaging@23.4.0 \
- @notifee/react-native@9.1.8 \
- @cometchat/chat-sdk-react-native@4.0.18 \
- @cometchat/calls-sdk-react-native@4.4.0 \
- @cometchat/chat-uikit-react-native@5.2.6 \
- @react-native-async-storage/async-storage@2.2.0 \
- react-native-callkeep@github:cometchat/react-native-callkeep \
- react-native-voip-push-notification@3.3.3
-```
-
-Match these or newer compatible versions in your app.
-
-## 4. Android App Setup
-
-### 4.1 Configure Firebase with Android credentials
-
-To allow Firebase on Android to use the credentials, the `google-services` plugin must be enabled on the project. This requires modification to two files in the Android directory.
-
-First, add the google-services plugin as a dependency inside of your `/android/build.gradle` file:
-
-```android lines
-buildscript {
- dependencies {
- // ... other dependencies
- classpath("com.google.gms:google-services:4.4.4")
- }
-}
-```
-
-Lastly, execute the plugin by adding the following to your `/android/app/build.gradle` file:
-
-```android lines
-apply plugin: 'com.android.application'
-apply plugin: 'com.google.gms.google-services'
-```
-
-### 4.2 Configure required permissions in `AndroidManifest.xml` as shown.
-
-```xml lines
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-and ask for runtime permissions where needed (e.g. `POST_NOTIFICATIONS` on Android 13+).
-
-```tsx lines
-import { PermissionsAndroid, Platform } from "react-native";
-
- const requestAndroidPermissions = async () => {
- if (Platform.OS !== 'android') return;
-
- try {
- // Ask for push‑notification permission
- const authStatus = await messaging().requestPermission();
- const enabled =
- authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
- authStatus === messaging.AuthorizationStatus.PROVISIONAL;
-
- if (!enabled) {
- console.warn('Notification permission denied (FCM).');
- }
- } catch (error) {
- console.warn('FCM permission request error:', error);
- }
-
- try {
- await PermissionsAndroid.requestMultiple([
- PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE,
- PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
- PermissionsAndroid.PERMISSIONS.CAMERA,
- PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
- PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS,
- ]);
- } catch (err) {
- console.warn('Android permissions error:', err);
- }
-}
-```
-
-### 4.3 Register FCM token with CometChat
-
-Inside your main app file where you initialize CometChat, add the below code snippet after the user has logged in successfully.
-Initilize and register the FCM token for Android as shown:
-
-```ts lines
-requestAndroidPermissions();
-
-const FCM_TOKEN = await messaging().getToken();
-console.log("FCM Token:", FCM_TOKEN);
-
-// For React Native Android
-CometChatNotifications.registerPushToken(
- FCM_TOKEN,
- CometChatNotifications.PushPlatforms.FCM_REACT_NATIVE_ANDROID,
- "YOUR_FCM_PROVIDER_ID" // from CometChat Dashboard
- )
- .then(() => {
- console.log("Token registration successful");
- })
- .catch((err) => {
- console.log("Token registration failed:", err);
- });
-```
-
-### 4.4 Unregister FCM token on logout
-
-Typically, push token unregistration should occur prior to user logout, using the `CometChat.logout()` method.
-For token unregistration, use the `CometChatNotifications.unregisterPushToken()` method provided by the SDKs.
-
-## 5. VoIP call notifications
-
-These steps are Android-only—copy/paste and fill your IDs.
-
-### 5.1 Add CallKeep services to `android/app/src/main/AndroidManifest.xml`
-Inside the `` tag add:
-
-```xml lines
-
-
-
-
-
-
-
-```
-
-### 5.2 Background handler for call pushes (`index.js`)
-Data-only FCM calls show the native dialer even when the app is killed.
-
-```js lines
-import messaging from "@react-native-firebase/messaging";
-import { Platform } from "react-native";
-import { CometChat } from "@cometchat/chat-sdk-react-native";
-import { voipHandler } from "./VoipNotificationHandler";
-import { displayLocalNotification } from "./LocalNotificationHandler";
-
-if (Platform.OS === "android") {
- messaging().setBackgroundMessageHandler(async remoteMessage => {
- const data = remoteMessage.data || {};
- if (data.type === "call") {
- await voipHandler.initialize();
- switch (data.callAction) {
- case "initiated":
- voipHandler.msg = data;
- await voipHandler.displayCallAndroid();
- break;
- case "ended":
- case "unanswered":
- case "busy":
- case "rejected":
- case "cancelled":
- CometChat.clearActiveCall();
- if (voipHandler?.callerId) {
- voipHandler.removeCallDialerWithUUID(voipHandler.callerId);
- }
- await voipHandler.endCall({ callUUID: voipHandler.callerId });
- break;
- case "ongoing":
- voipHandler.displayNotification({
- title: data?.receiverName || "",
- body: "ongoing call",
- });
- break;
- default:
- break;
- }
- return;
- }
- await displayLocalNotification(remoteMessage);
- });
-}
-```
-
-### 5.3 Drop in `VoipNotificationHandler.ts`
-Handles CallKeep setup, shows the incoming call UI, accepts/rejects via CometChat, and defers acceptance if login/navigation isn’t ready.
-
-```ts lines
-import { Platform } from "react-native";
-import notifee, { AndroidImportance } from "@notifee/react-native";
-import RNCallKeep, { IOptions } from "react-native-callkeep";
-import { CometChat } from "@cometchat/chat-sdk-react-native";
-import { setPendingAnsweredCall } from "./PendingCallManager";
-
-const options: IOptions = {
- android: {
- alertTitle: "VoIP permissions",
- alertDescription: "Allow phone account access to show incoming calls",
- cancelButton: "Cancel",
- okButton: "OK",
- imageName: "ic_notification",
- additionalPermissions: [],
- foregroundService: {
- channelId: "com.cometchat.sampleapp.reactnative.android",
- channelName: "Sampleapp Channel",
- notificationTitle: "Sampleapp is running in the background",
- },
- },
- ios: { appName: "Sampleapp" },
-};
-
-function uuid() {
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
- const r = Math.floor(Math.random() * 16);
- const v = c === "x" ? r : (r & 0x3) | 0x8;
- return v.toString(16);
- });
-}
-
-class VoipNotificationHandler {
- channelId = "";
- isRinging = false;
- isAnswered = false;
- pendingAcceptance = false;
- callerId = "";
- msg: any = {};
- initialized = false;
- private setupPromise: Promise | null = null;
- private listenersAttached = false;
-
- async initialize() {
- if (this.initialized && this.setupPromise) {
- await this.setupPromise;
- return;
- }
- if (!this.setupPromise) {
- this.setupPromise = (async () => {
- if (Platform.OS === "android") {
- await this.createNotificationChannel();
- }
- await this.getPermissions();
- this.setupEventListeners();
- this.initialized = true;
- })().catch((err) => {
- this.setupPromise = null;
- throw err;
- });
- }
- await this.setupPromise;
- }
-
- async getPermissions() {
- await RNCallKeep.setup(options);
- RNCallKeep.setAvailable(true);
- RNCallKeep.setReachable();
- try {
- await RNCallKeep.checkPhoneAccountEnabled();
- } catch {}
- }
-
- async createNotificationChannel() {
- this.channelId = await notifee.createChannel({
- id: "message",
- name: "Messages",
- lights: true,
- vibration: true,
- importance: AndroidImportance.HIGH,
- });
- }
-
- async displayNotification({
- title,
- body,
- data,
- }: {
- title: string;
- body: string;
- data?: any;
- }) {
- if (Platform.OS === "android" && !this.channelId)
- await this.createNotificationChannel();
- await notifee.displayNotification({
- title,
- body,
- data,
- android: this.channelId
- ? { channelId: this.channelId, smallIcon: "ic_launcher" }
- : undefined,
- });
- }
-
- async displayCallAndroid() {
- if (this.isAnswered || this.pendingAcceptance) return;
- await this.initialize();
- this.isRinging = true;
- this.callerId = uuid();
- const callerName = this.msg?.senderName || "Incoming Call";
- await RNCallKeep.displayIncomingCall(
- this.callerId,
- callerName,
- callerName,
- "generic",
- );
- }
-
- onAnswerCall = async ({ callUUID }: { callUUID: string }) => {
- if (this.isAnswered) return;
- this.isRinging = false;
- this.isAnswered = true;
- const sessionID = this.msg?.sessionId;
- if (!sessionID) return;
-
- setTimeout(async () => {
- const loggedInUser = await CometChat.getLoggedinUser().catch(() => null);
- if (!loggedInUser) {
- this.pendingAcceptance = true;
- await setPendingAnsweredCall({
- sessionId: sessionID,
- raw: this.msg,
- storedAt: Date.now(),
- });
- try {
- RNCallKeep.backToForeground();
- } catch (err) {
- // Activity may not exist yet if app was killed - the pending call will be handled when app opens
- console.log(
- "[VoIP] backToForeground failed, pending call saved:",
- err,
- );
- }
- return;
- }
- try {
- await CometChat.acceptCall(sessionID);
- } catch (error: any) {
- if (error?.code !== "ERR_CALL_USER_ALREADY_JOINED") throw error;
- }
- RNCallKeep.endAllCalls();
- this.pendingAcceptance = false;
- }, 600);
- };
-
- endCall = async ({ callUUID }: { callUUID: string }) => {
- if (this.msg?.type === "call") {
- const sessionID = this.msg.sessionId;
- if (this.isAnswered && sessionID) {
- this.isAnswered = false;
- CometChat.endCall(sessionID);
- } else if (sessionID) {
- const loggedInUser = await CometChat.getLoggedinUser().catch(
- () => null,
- );
- if (loggedInUser) {
- setTimeout(() => {
- CometChat.rejectCall(sessionID, CometChat.CALL_STATUS.REJECTED);
- }, 300);
- }
- }
- }
- const id = callUUID || this.callerId;
- if (id) RNCallKeep.endCall(id);
- RNCallKeep.endAllCalls();
- this.isRinging = false;
- this.isAnswered = false;
- this.pendingAcceptance = false;
- this.callerId = "";
- this.msg = {};
- };
-
- removeCallDialerWithUUID = (callerId: string) => {
- const id = callerId || this.callerId;
- if (id) RNCallKeep.reportEndCallWithUUID(id, 6);
- };
-
- setupEventListeners() {
- if (this.listenersAttached) return;
- RNCallKeep.addEventListener("answerCall", this.onAnswerCall);
- RNCallKeep.addEventListener("endCall", this.endCall);
- RNCallKeep.addEventListener("didDisplayIncomingCall", ({ callUUID }) => {
- if (callUUID) this.callerId = callUUID;
- this.isRinging = true;
- });
- this.listenersAttached = true;
- }
-}
-
-export const voipHandler = new VoipNotificationHandler();
-```
-
-### 5.4 Add `PendingCallManager.ts`
-Stores an answered call during cold-start so you can accept it once login/navigation is ready.
-
-```ts lines
-import AsyncStorage from "@react-native-async-storage/async-storage";
-
-export interface PendingAnsweredCallPayload {
- sessionId: string;
- raw: any;
- storedAt: number;
-}
-
-let inMemoryPending: PendingAnsweredCallPayload | null = null;
-const STORAGE_KEY = "pendingAnsweredCall";
-
-export async function setPendingAnsweredCall(payload: PendingAnsweredCallPayload) {
- inMemoryPending = payload;
- try { await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(payload)); } catch {}
-}
-
-export async function consumePendingAnsweredCall(): Promise {
- if (inMemoryPending) {
- const tmp = inMemoryPending;
- inMemoryPending = null;
- try { await AsyncStorage.removeItem(STORAGE_KEY); } catch {}
- return tmp;
- }
- try {
- const raw = await AsyncStorage.getItem(STORAGE_KEY);
- if (raw) {
- await AsyncStorage.removeItem(STORAGE_KEY);
- const parsed: PendingAnsweredCallPayload = JSON.parse(raw);
- inMemoryPending = null;
- return parsed;
- }
- } catch {}
- return null;
-}
-
-export function isPendingStale(p: PendingAnsweredCallPayload, maxAgeMs = 2 * 60 * 1000) {
- return Date.now() - p.storedAt > maxAgeMs;
-}
-```
-
-### 5.5 Wire `App.tsx` to init VoIP + consume pending accepts
-Add this after CometChat init/login:
-
-```ts lines
-import { Platform } from "react-native";
-import messaging from "@react-native-firebase/messaging";
-import { CometChat, CometChatNotifications } from "@cometchat/chat-sdk-react-native";
-import { voipHandler } from "./VoipNotificationHandler";
-import { consumePendingAnsweredCall, isPendingStale } from "./PendingCallManager";
-
-if (Platform.OS === "android") {
- const fcmToken = await messaging().getToken();
- await CometChatNotifications.registerPushToken(
- fcmToken,
- CometChatNotifications.PushPlatforms.FCM_REACT_NATIVE_ANDROID,
- "YOUR_FCM_PROVIDER_ID"
- );
-}
-
-useEffect(() => {
- if (Platform.OS === "android" && loggedIn) {
- const t = setTimeout(() => voipHandler.initialize(), 3000);
- return () => clearTimeout(t);
- }
-}, [loggedIn]);
-
-// Handle pending calls in a useEffect
-useEffect(() => {
- const handlePendingCall = async () => {
- const pending = await consumePendingAnsweredCall();
- if (pending && !isPendingStale(pending)) {
- try {
- await CometChat.acceptCall(pending.sessionId);
- } catch (err) {
- console.log(err);
- }
- }
- };
- handlePendingCall();
-}, []);
-```
-
-### 5.6 Call push payload (FCM data)
-Send a data-only FCM message like:
-
-```json
-{
- "to": "",
- "priority": "high",
- "data": {
- "type": "call",
- "callAction": "initiated",
- "sessionId": "",
- "senderName": "Alice",
- "receiverName": "Bob"
- }
-}
-```
-
-### 5.7 Local notification helper (`LocalNotificationHandler.ts`)
-> Ensure `@notifee/react-native` is installed (listed in Dependencies above).
-Add this helper next to your `index.js` to show local alerts for non-call pushes:
-
-```ts lines
-import { Platform } from "react-native";
-import notifee, { AndroidImportance } from "@notifee/react-native";
-
-const CHANNEL_ID = "default";
-
-async function ensureChannel(): Promise {
- if (Platform.OS !== "android") return undefined;
- return notifee.createChannel({
- id: CHANNEL_ID,
- name: "Default",
- lights: true,
- vibration: true,
- importance: AndroidImportance.HIGH,
- });
-}
-
-export async function displayLocalNotification(remoteMessage: any) {
- try {
- const { notification = {}, data = {} } = remoteMessage || {};
- const title = notification?.title || data?.title || "Notification";
- const body = notification?.body || data?.body || "";
-
- if (Platform.OS === "ios") {
- await notifee.requestPermission();
- }
-
- const channelId = await ensureChannel();
-
- await notifee.displayNotification({
- title,
- body,
- data,
- android: channelId
- ? {
- channelId,
- pressAction: { id: "default" },
- importance: AndroidImportance.HIGH,
- smallIcon: "ic_launcher",
- }
- : undefined,
- });
- } catch (error) {
- console.error("[LocalNotificationHandler] Failed to display notification", error);
- }
-}
-```
-- For a proper notification icon, create a dedicated `ic_notification.xml` (vector) or PNG in `android/app/src/main/res/drawable/`; Android expects a white glyph with transparency for best results.
-
-## 6. Handling notification taps and navigation
-
-To handle notification taps and navigate to the appropriate chat screen, you need to set up handlers for both foreground and background notifications.
-
-{/* :TODO: Add code snippets and explanation for setting up Notifee handlers and navigation logic. */}
-
-
-## 7. Badge Count Implementation
-
-CometChat's Enhanced Push Notification payload includes an `unreadMessageCount` field that represents the total number of unread messages across all conversations for the logged-in user. You can use this value to update the app icon badge, providing users with a visual indicator of unread messages.
-
-### 7.1 Enable Unread Badge Count on the CometChat Dashboard
-
-
-
- Go to **CometChat Dashboard → Notifications Engine → Settings → Preferences → Push Notification Preferences**.
-
-
- Scroll down and enable the **Unread Badge Count** toggle.
-
-
-
-Once enabled, CometChat automatically includes the `unreadMessageCount` field in every push payload sent to your app.
-
-### 7.2 Expected Payload Format
-
-CometChat sends push notifications with the following structure:
-
-```json
-{
- "data": {
- "unreadMessageCount": "5",
- "title": "New Message",
- "body": "John: Hello!",
- "conversationId": "user_abc123",
- "receiverType": "user",
- "type": "chat"
- }
-}
-```
-
-
-The `unreadMessageCount` field is a **string** representing the total unread messages across all conversations for the logged-in user.
-
-
-### 7.3 Handle Badge Count in Background Messages
-
-Update your FCM background message handler in `index.js` to extract and set the badge count:
-
-```javascript
-import messaging from "@react-native-firebase/messaging";
-import notifee from "@notifee/react-native";
-
-messaging().setBackgroundMessageHandler(async (remoteMessage) => {
- const data = remoteMessage.data || {};
-
- // Extract and set badge count from push payload
- const unreadCount = data?.unreadMessageCount;
- if (unreadCount !== undefined && unreadCount !== null) {
- const count = parseInt(unreadCount, 10);
- if (!isNaN(count) && count >= 0) {
- try {
- await notifee.setBadgeCount(count);
- console.log("Badge count updated (Android):", count);
- } catch (error) {
- console.error("Error setting badge:", error);
- }
- }
- }
-
- // Display local notification
- await displayLocalNotification(remoteMessage);
-});
-```
-
-### 7.4 Handle Badge Count in Foreground Messages
-
-In your `App.tsx`, set up a listener for foreground FCM messages:
-
-```typescript
-import messaging from "@react-native-firebase/messaging";
-import notifee from "@notifee/react-native";
-
-useEffect(() => {
- if (Platform.OS === "android") {
- const unsubscribe = messaging().onMessage(async (remoteMessage) => {
- // Extract and set badge count from push payload
- const unreadCount = remoteMessage.data?.unreadMessageCount;
- if (unreadCount !== undefined && unreadCount !== null) {
- const count = parseInt(unreadCount as string, 10);
- if (!isNaN(count) && count >= 0) {
- try {
- await notifee.setBadgeCount(count);
- console.log("Badge count updated (Android):", count);
- } catch (error) {
- console.error("Error setting badge:", error);
- }
- }
- }
-
- // Display local notification
- await displayLocalNotification(remoteMessage);
- });
-
- return () => unsubscribe();
- }
-}, []);
-```
-
-### 7.5 Display Local Notification with Badge Count
-
-Update your notification display function to include the badge count:
-
-```typescript
-import notifee, { AndroidImportance } from "@notifee/react-native";
-
-export async function displayLocalNotification(remoteMessage: any) {
- const { title, body, senderAvatar } = remoteMessage.data || {};
-
- // Create notification channel
- const channelId = await notifee.createChannel({
- id: "chat-messages",
- name: "Chat Messages",
- vibration: true,
- importance: AndroidImportance.HIGH,
- });
-
- // Parse badge count from payload
- const unreadCount = remoteMessage.data?.unreadMessageCount;
- const badgeCount = unreadCount ? parseInt(unreadCount, 10) : undefined;
-
- // Optionally enhance title with unread count
- const displayTitle =
- badgeCount && badgeCount > 1
- ? `${title || "New Message"} (${badgeCount} unread)`
- : title || "New Message";
-
- // Update badge count
- if (badgeCount && badgeCount > 0) {
- await notifee.setBadgeCount(badgeCount);
- }
-
- // Display notification with fixed ID to prevent badge accumulation
- // on devices that sum badge counts from multiple notifications
- await notifee.displayNotification({
- id: "chat-notification",
- title: displayTitle,
- body: body || "You received a new message.",
- android: {
- channelId,
- autoCancel: true,
- smallIcon: "ic_notification",
- largeIcon:
- senderAvatar ||
- "https://cdn-icons-png.flaticon.com/512/149/149071.png",
- importance: AndroidImportance.HIGH,
- badgeCount: badgeCount,
- pressAction: {
- id: "default",
- },
- },
- data: {
- receiverType: remoteMessage.data?.receiverType,
- sender: remoteMessage.data?.sender,
- conversationId: remoteMessage.data?.conversationId,
- },
- });
-}
-```
-
-### 7.6 Clear Badge When App Becomes Active
-
-Clear all notifications and reset the badge when the app returns to the foreground:
-
-```typescript
-import { AppState, AppStateStatus, Platform } from "react-native";
-import notifee from "@notifee/react-native";
-
-useEffect(() => {
- const handleAppStateChange = async (nextState: AppStateStatus) => {
- if (nextState === "active" && Platform.OS === "android") {
- // Clear all notifications (also resets badge count)
- await notifee.cancelAllNotifications();
- console.log("Notifications cleared (Android)");
- }
- };
-
- const subscription = AppState.addEventListener("change", handleAppStateChange);
- return () => subscription.remove();
-}, []);
-```
-
-### 7.7 Clear Badge on Logout
-
-When a user logs out, clear the badge so it doesn't show a stale count on the login screen or for the next user:
-
-```typescript
-import notifee from "@notifee/react-native";
-import { CometChat, CometChatNotifications } from "@cometchat/chat-sdk-react-native";
-
-const handleLogout = async () => {
- // Unregister push token first
- await CometChatNotifications.unregisterPushToken();
-
- // Clear badge before logout
- await notifee.setBadgeCount(0);
- await notifee.cancelAllNotifications();
-
- // Logout from CometChat
- await CometChat.logout();
- console.log("User logged out, badge cleared");
-};
-```
-
-### 7.8 Clear Badge on Fresh Install / No Logged-In User
-
-Clear the badge during app initialization when no user is logged in. This handles cases where badge count may persist after app reinstall:
-
-```typescript
-import notifee from "@notifee/react-native";
-import { CometChat } from "@cometchat/chat-sdk-react-native";
-
-// During app initialization, after CometChat.init()
-const initializeApp = async () => {
- // Initialize CometChat first
- await CometChatUIKit.init(uiKitSettings);
-
- // Check if user is logged in
- const loggedInUser = await CometChat.getLoggedinUser();
-
- if (!loggedInUser) {
- // No user logged in - clear any stale badge
- await notifee.setBadgeCount(0);
- await notifee.cancelAllNotifications();
- console.log("No logged-in user, badge cleared");
- }
-};
-```
-
-### 7.9 Clear Badge in Login Listener (Safety Net)
-
-Register a login listener to clear the badge on logout as a backup mechanism:
-
-```typescript
-import notifee from "@notifee/react-native";
-import { CometChat } from "@cometchat/chat-sdk-react-native";
-
-useEffect(() => {
- const listenerID = "BADGE_LOGOUT_LISTENER";
-
- CometChat.addLoginListener(
- listenerID,
- new CometChat.LoginListener({
- logoutOnSuccess: async () => {
- // Safety net: clear badge when logout succeeds
- await notifee.setBadgeCount(0);
- await notifee.cancelAllNotifications();
- console.log("Logout listener: badge cleared");
- },
- })
- );
-
- return () => {
- CometChat.removeLoginListener(listenerID);
- };
-}, []);
-```
-
-### 7.10 Key Implementation Notes
-
-| Consideration | Details |
-| --- | --- |
-| **Backend-driven badge count** | The `unreadMessageCount` value comes directly from CometChat's backend via the push payload, ensuring consistency across all devices. |
-| **Fixed notification ID** | Using a fixed notification ID (`'chat-notification'`) prevents certain devices from accumulating badge counts across multiple notifications. The badge always reflects the exact `unreadMessageCount` from the backend. |
-| **Clear on app active** | Always clear the badge when the app becomes active. New notifications will update the badge with the fresh `unreadMessageCount` from the backend. |
-| **Clear on logout** | Always clear the badge when a user logs out to prevent stale counts for the next user. |
-| **Clear on fresh install** | Clear the badge during app initialization when no user is logged in to handle reinstall scenarios. |
-| **Login listener safety net** | Use CometChat's login listener as a backup to ensure badge is cleared on logout. |
-| **Title enhancement** | Optionally display the unread count in the notification title (e.g., "John (5 unread)") for devices that don't support app icon badges. |
-
-## 8. Testing Checklist
-
-1. Install on a physical Android device, grant `POST_NOTIFICATIONS` permission, log in, and verify FCM token registration succeeds.
-2. Send a message from another user:
- - **Foreground:** Notifee banner appears unless that chat is already open.
- - **Background/terminated:** Tap opens the correct conversation; Notifee background handler runs.
-3. **VoIP call:** Send a `callAction=initiated` push; expect the native dialer to appear. Answer and verify the call connects; send `callAction=ended` to dismiss it.
-4. Rotate tokens (reinstall or revoke) and confirm `onTokenRefresh` re-registers the new token.
-
-## 9. Troubleshooting
-
-| Symptom | Quick Checks |
-| --- | --- |
-| No pushes | Confirm `google-services.json` location, package IDs match Firebase, Push extension enabled with correct provider IDs, permissions granted. |
-| Token registration fails | Ensure registration runs **after login**, provider IDs are set, and `registerDeviceForRemoteMessages()` is called. |
-{/* | Notification taps do nothing | Keep Notifee foreground/background handlers and ensure the navigation ref is ready before routing. | */}
-{/* | Call UI not showing | Verify CallKeep setup, telecom permissions, and that `VoipNotificationHandler.initialize()` runs post-login. | */}
-{/* | Inline reply needed | Extend Notifee action buttons; CometChat expects you to send the message manually after reading `remoteMessage.data`. | */}
-
----
-
-## Next Steps
-
-
-
-Set up APNs push notifications for iOS
-
-
-Strip HTML tags and customize notification content
-
-
-Learn how to send different types of messages
-
-
-Handle incoming messages in real time
-
-
diff --git a/notifications/react-native-push-notifications-ios.mdx b/notifications/react-native-push-notifications-ios.mdx
deleted file mode 100644
index f5489fc6e..000000000
--- a/notifications/react-native-push-notifications-ios.mdx
+++ /dev/null
@@ -1,846 +0,0 @@
----
-title: "React Native Push Notifications (iOS)"
-description: "Bring the SampleAppWithPushNotifications experience—APNs + VoIP—into any React Native project using CometChat UI Kit."
----
-
-
-
-| Field | Value |
-| --- | --- |
-| Platform | iOS (APNs + PushKit/CallKit) |
-| Key Classes | `CometChatNotifications`, `VoipNotificationHandler`, `PendingCallManager` |
-| Key Methods | `registerPushToken()`, `unregisterPushToken()`, `PushNotificationIOS.requestPermissions()` |
-| Push Platforms | `APNS_REACT_NATIVE_DEVICE`, `APNS_REACT_NATIVE_VOIP` |
-| Prerequisites | CometChat SDK initialized, user logged in, APNs `.p8` key uploaded, physical iOS device |
-
-
-
-
- Reference implementation of React Native UI Kit and APNs Push Notification setup.
-
-
-## What this guide covers
-
-- CometChat Dashboard setup (enable push, add APNs provider).
-- Platform credentials (Apple entitlements).
-- Copying the sample notification stack and aligning IDs/provider IDs.
-- Native glue for iOS (capabilities + PushKit/CallKit for VoIP).
-- Token registration, navigation from pushes, testing, and troubleshooting.
-
-## What you need first
-
-- CometChat app credentials (App ID, Region, Auth Key) and Push Notifications enabled with an **APNs provider (React Native iOS)**; add an **APNs VoIP provider** if you plan to receive call invites via PushKit.
-- Apple push setup: APNs `.p8` key/cert in CometChat, iOS project with Push Notifications + Background Modes (Remote notifications) permissions.
-- React Native 0.81+, Node 18+, physical iOS device for reliable push/call testing.
-
-## How APNs + CometChat work together
-
-- **APNs (iOS) is the transport:** Apple issues the APNs token and delivers payloads to devices.
-- **CometChat provider holds your credentials:** The APNs provider you add stores your `.p8` key/cert.
-- **Registration flow:** Request permission → APNs returns token → after `CometChat.login`, register with `CometChatNotifications.registerPushToken(token, platform, providerId)` using `APNS_REACT_NATIVE_DEVICE` → CometChat sends pushes to APNs on your behalf → the app handles taps/foreground events via `PushNotificationIOS`.
-
-## 1. Enable push and add providers (CometChat Dashboard)
-
-1. Go to **Notifications → Settings** and enable **Push Notifications**.
-
-
-
-
-
-2. Add an **APNs** provider for iOS and copy the Provider ID.
-
-
-
-
-
-## 2. Prepare platform credentials
-
-### Apple Developer portal
-
-For iOS we use Apple Push Notification service (APNs) for both standard and VoIP pushes. Follow these steps to create the credentials you’ll upload to CometChat.
-
-
-
- 1. Open **Keychain Access** → Certificate Assistant → *Request a Certificate From a Certificate Authority*.
-
-
-
- 2. In **Certificate Information**, enter your Apple Developer email and a common name; choose **Saved to disk**, then **Continue**.
- 3. Save the CSR file locally—this contains your public/private key pair.
-
-
-
- 1. Sign in to the [Apple Developer Member Center](https://developer.apple.com/membercenter) → **Certificates, Identifiers & Profiles**.
-
-
-
- 2. Click **+** to add a certificate.
-
-
-
- 3. Under **Services**, pick **Apple Push Notification service SSL (Sandbox & Production)**.
-
-
-
- 4. Select your App ID, upload the CSR, continue, and download the generated `.cer` file.
-
-
-
-
- &
-
-
-
-
-
- &
-
-
-
-
-
-
-
- 1. In **Certificates, IDs & Profiles**, open **Keys** → click **+**.
- 2. Enter a key name, check **Apple Push Notification service (APNs)**, then **Continue** → **Register**.
- 3. Download the `.p8` file and note the **Key ID**, **Team ID**, and your **Bundle ID**—you’ll enter these in CometChat.
- 4. *(Optional)* If you still use `.p12`, export it from the downloaded key without an export password; keep it handy for upload.
-
-
- **`.p12` certificates are deprecated.** Apple recommends using `.p8` Auth Keys for push notifications. `.p8` keys are simpler to manage (one key works for all your apps), never expire, and are the only format actively supported going forward. Migrate to `.p8` if you haven't already.
-
-
-
-
-Enable **Push Notifications** plus **Background Modes → Remote notifications** on the bundle ID.
-
-
-
-
-
-## 3. Local configuration
-
-- Update `src/utils/AppConstants.tsx` with `appId`, `authKey`, `region`, and `apnProviderId`.
-- Keep `app.json` name consistent with your bundle ID / applicationId.
-
-```ts lines
-const APP_ID = "";
-const AUTH_KEY = "";
-const REGION = "";
-const DEMO_UID = "cometchat-uid-1";
-```
-
-### 3.1 Dependencies snapshot (from Sample App)
-
-Install these dependencies in your React Native app:
-
-```npm lines
-npm install \
- @cometchat/chat-sdk-react-native@4.0.18 \
- @cometchat/calls-sdk-react-native@4.4.0 \
- @cometchat/chat-uikit-react-native@5.2.6 \
- @notifee/react-native@9.1.8 \
- @react-native-async-storage/async-storage@2.2.0 \
- @react-native-community/push-notification-ios@1.12.0 \
- react-native-push-notification@8.1.1 \
- react-native-callkeep@4.3.16 \
- react-native-voip-push-notification@3.3.3
-```
-
-Match these or newer compatible versions in your app.
-
-## 4. iOS App setup
-
-### 4.1 Project Setup
-
-Enable **Push Notifications** and **Background Modes** (Remote notifications) in Xcode.
-
-
-
-
-
-### 4.2 Install dependencies + pods
-
-After running the npm install above, install pods from the `ios` directory:
-```bash lines
-cd ios
-pod install
-```
-
-### 4.3 AppDelegate.swift modifications:
-
-Add imports at the top:
-```swift lines
-import UserNotifications
-import RNCPushNotificationIOS
-```
-
-Add `UNUserNotificationCenterDelegate` to the `AppDelegate` class declaration:
-```swift
-class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate
-```
-
-Add the following inside the `didFinishLaunchingWithOptions` method:
-```swift lines
-UNUserNotificationCenter.current().delegate = self
-
-UNUserNotificationCenter.current().requestAuthorization(
- options: [.alert, .badge, .sound]
-) {
- granted,
- error in
- if granted {
- DispatchQueue.main.async {
- application.registerForRemoteNotifications()
- }
- } else {
- print("Push Notification permission not granted: \(String(describing: error))")
- }
-}
-```
-
-Add the following methods to handle push notification events:
-```swift lines
-func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
- print("APNs device token received: \(deviceToken)")
- RNCPushNotificationIOS.didRegisterForRemoteNotifications(withDeviceToken: deviceToken)
-}
-
-func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
- print("APNs registration failed: \(error)")
- RNCPushNotificationIOS.didFailToRegisterForRemoteNotificationsWithError(error)
-}
-
-func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
- RNCPushNotificationIOS.didReceiveRemoteNotification(userInfo, fetchCompletionHandler: completionHandler)
-}
-
-func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
- completionHandler([.banner, .sound, .badge])
-}
-
-func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
- RNCPushNotificationIOS.didReceive(response)
- completionHandler()
-}
-```
-
-Add the following to `Podfile` to avoid framework linkage issues:
-```ruby
-use_frameworks! :linkage => :static
-```
-
-You might have to remove below code if already present in your Podfile:
-```ruby lines
-linkage = ENV['USE_FRAMEWORKS']
-if linkage != nil
- Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
- use_frameworks! :linkage => linkage.to_sym
-end
-```
-
-Then lets install pods and open the workspace:
-```bash lines
-cd ios
-pod install
-open YourProjectName.xcworkspace
-```
-
-### 4.4 App.tsx modifications:
-
-Import CometChatNotifications and PushNotificationIOS:
-
-```tsx
-import { CometChat, CometChatNotifications } from "@cometchat/chat-sdk-react-native";
-import PushNotificationIOS from "@react-native-community/push-notification-ios";
-```
-
-Get device token and store it in a ref:
-Also, define your APNs provider ID from the CometChat Dashboard.
-And request permissions on mount:
-
-```tsx lines
-const APNS_PROVIDER_ID = 'YOUR_APNS_PROVIDER_ID'; // from CometChat Dashboard
-const apnsTokenRef = useRef < string | null > (null);
-
-useEffect(() => {
- if (Platform.OS !== 'ios') return;
-
- const onRegister = (deviceToken: string) => {
- console.log(' APNs device token captured:', deviceToken);
- apnsTokenRef.current = deviceToken;
- };
-
- PushNotificationIOS.addEventListener('register', onRegister);
-
- PushNotificationIOS.addEventListener('registrationError', error => {
- console.error(' APNs registration error:', error);
- });
-
- // Trigger permission + native registration
- PushNotificationIOS.requestPermissions().then(p =>
- console.log('Push permissions:', p),
- );
-
- return () => {
- PushNotificationIOS.removeEventListener('register');
- PushNotificationIOS.removeEventListener('registrationError');
- };
-}, []);
-```
-
-After user login, register the APNs token:
-```tsx lines
-// Register token ONLY if we already have it
-if (apnsTokenRef.current) {
- await CometChatNotifications.registerPushToken(
- apnsTokenRef.current,
- CometChatNotifications.PushPlatforms.APNS_REACT_NATIVE_DEVICE,
- APNS_PROVIDER_ID
- );
- console.log(' APNs token registered with CometChat');
-}
-```
-
-Prior to logout, unregister the APNs token:
-```tsx
-await CometChatNotifications.unregisterPushToken();
-```
-
-## 5. VoIP call notifications (iOS)
-
-These steps are iOS-only—copy/paste and fill your IDs.
-
-### 5.1 Enable capabilities in Xcode
-- Target ➜ Signing & Capabilities: add **Push Notifications**.
-- Add **Background Modes** → enable **Voice over IP** and **Remote notifications**.
-- Run on a real device (PushKit/CallKit don’t work on the simulator).
-
-### 5.2 AppDelegate.swift (PushKit + CallKit bridge)
-Update your `AppDelegate` to register for VoIP pushes ASAP and forward events to JS/CallKeep:
-
-```swift lines
-import PushKit
-import RNVoipPushNotification
-import RNCallKeep
-// ...
-@main
-class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, PKPushRegistryDelegate {
- // ...
- func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
- // existing UNUserNotificationCenter code ...
- RNVoipPushNotificationManager.voipRegistration() // triggers PushKit token
- return true
- }
-
- // APNs device token handlers stay unchanged
-
- // PushKit token -> JS
- func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
- RNVoipPushNotificationManager.didUpdate(pushCredentials, forType: type.rawValue)
- }
-
- // Incoming VoIP push -> CallKit + JS
- func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
- let dict = payload.dictionaryPayload
- let uuid = (dict["uuid"] as? String) ?? UUID().uuidString
- RNVoipPushNotificationManager.addCompletionHandler(uuid, completionHandler: completion)
- RNVoipPushNotificationManager.didReceiveIncomingPush(with: payload, forType: type.rawValue)
- RNCallKeep.reportNewIncomingCall(uuid, handle: (dict["handle"] as? String) ?? "Unknown", handleType: "generic", hasVideo: false, localizedCallerName: (dict["callerName"] as? String) ?? "Incoming Call", supportsHolding: true, supportsDTMF: true, supportsGrouping: true, supportsUngrouping: true, fromPushKit: true, payload: nil)
- }
-}
-```
-
-### 5.3 Drop in `VoipNotificationHandler.ts`
-Handles CallKeep UI, defers acceptance until login, and listens for PushKit events.
-
-```ts lines
-import { Platform } from "react-native";
-import notifee, { AndroidImportance } from "@notifee/react-native";
-import RNCallKeep, { IOptions } from "react-native-callkeep";
-import { CometChat } from "@cometchat/chat-sdk-react-native";
-import VoipPushNotification from "react-native-voip-push-notification";
-import { setPendingAnsweredCall } from "./PendingCallManager";
-
-const options: IOptions = {
- ios: { appName: "YourAppName" },
- android: { alertTitle: "VOIP required", alertDescription: "Allow phone account access", cancelButton: "Cancel", okButton: "OK", imageName: "ic_notification" },
-};
-
-type IncomingPayload = { sessionId?: string; senderName?: string; callerName?: string; name?: string; type?: string; [k: string]: any; };
-
-class VoipNotificationHandler {
- channelId = "";
- isRinging = false;
- isAnswered = false;
- pendingAcceptance = false;
- callerId = "";
- msg: IncomingPayload | null = null;
- initialized = false;
- private setupPromise: Promise | null = null;
- private listenersAttached = false;
- private lastSessionId: string | null = null;
- private lastRingAt = 0;
-
- async initialize() {
- if (this.initialized && this.setupPromise) { await this.setupPromise; return; }
- if (!this.setupPromise) {
- this.setupPromise = (async () => {
- if (Platform.OS === "android") { await this.createNotificationChannel(); }
- await this.setupCallKeep();
- this.setupEventListeners();
- this.initialized = true;
- })().catch(err => { this.setupPromise = null; throw err; });
- }
- await this.setupPromise;
- }
-
- private async setupCallKeep() {
- await RNCallKeep.setup(options);
- RNCallKeep.setAvailable(true);
- if (Platform.OS === "android") { RNCallKeep.setReachable(); }
- }
-
- private async createNotificationChannel() {
- this.channelId = await notifee.createChannel({ id: "message", name: "Messages", lights: true, vibration: true, importance: AndroidImportance.HIGH });
- }
-
- async displayIncomingCall(payload: IncomingPayload) {
- this.msg = payload || {};
- const sessionId = this.msg?.sessionId;
- const now = Date.now();
- if (sessionId && this.lastSessionId === sessionId && now - this.lastRingAt < 5000) return;
- if (this.isAnswered || this.pendingAcceptance) return;
- await this.initialize();
-
- const callerName = this.msg?.senderName || this.msg?.callerName || this.msg?.name || "Incoming Call";
- this.callerId = this.callerId || Math.random().toString();
- this.isRinging = true;
-
- await RNCallKeep.displayIncomingCall(this.callerId, callerName, callerName, "generic", true);
- this.lastSessionId = sessionId || null;
- this.lastRingAt = now;
- }
-
- onAnswerCall = async ({ callUUID }: { callUUID: string }) => {
- if (this.isAnswered) return;
- this.isRinging = false; this.isAnswered = true;
- const sessionID = this.msg?.sessionId; if (!sessionID) return;
- RNCallKeep.backToForeground();
- setTimeout(async () => {
- const loggedInUser = await CometChat.getLoggedinUser().catch(() => null);
- if (!loggedInUser) { this.pendingAcceptance = true; await setPendingAnsweredCall({ sessionId: sessionID, raw: this.msg, storedAt: Date.now() }); return; }
- try { await CometChat.acceptCall(sessionID); } catch (error: any) { if (error?.code !== "ERR_CALL_USER_ALREADY_JOINED") throw error; }
- RNCallKeep.endAllCalls(); this.pendingAcceptance = false;
- }, 350);
- };
-
- endCall = async ({ callUUID }: { callUUID: string }) => {
- const sessionID = this.msg?.sessionId;
- if (sessionID) {
- const loggedInUser = await CometChat.getLoggedinUser().catch(() => null);
- if (this.isAnswered) { await CometChat.endCall(sessionID).catch(() => {}); }
- else if (loggedInUser) { await CometChat.rejectCall(sessionID, CometChat.CALL_STATUS.REJECTED).catch(() => {}); }
- }
- const id = callUUID || this.callerId;
- if (id) RNCallKeep.endCall(id);
- RNCallKeep.endAllCalls();
- this.isRinging = false; this.isAnswered = false; this.pendingAcceptance = false; this.callerId = ""; this.msg = null; this.lastSessionId = null; this.lastRingAt = 0;
- };
-
- setupEventListeners() {
- if (this.listenersAttached) return;
- if (Platform.OS === "ios") {
- VoipPushNotification.addEventListener("notification", (notification: any) => this.displayIncomingCall(notification));
- VoipPushNotification.addEventListener("didLoadWithEvents", (events: any[]) => {
- (events || []).forEach(event => {
- if (event?.name === VoipPushNotification.RNVoipPushRemoteNotificationReceivedEvent) {
- this.displayIncomingCall(event.data);
- }
- });
- });
- }
- RNCallKeep.addEventListener("answerCall", this.onAnswerCall);
- RNCallKeep.addEventListener("endCall", this.endCall);
- RNCallKeep.addEventListener("didDisplayIncomingCall", ({ callUUID }) => { if (callUUID) this.callerId = callUUID; this.isRinging = true; });
- this.listenersAttached = true;
- }
-}
-
-export const voipHandler = new VoipNotificationHandler();
-```
-
-### 5.4 Add `PendingCallManager.ts`
-Stores an answered call during cold start so you can accept it after login/navigation is ready.
-
-```ts lines
-import AsyncStorage from "@react-native-async-storage/async-storage";
-
-export interface PendingAnsweredCallPayload { sessionId: string; raw: any; storedAt: number; }
-let inMemoryPending: PendingAnsweredCallPayload | null = null;
-const STORAGE_KEY = "pendingAnsweredCall";
-
-export async function setPendingAnsweredCall(payload: PendingAnsweredCallPayload) {
- inMemoryPending = payload; try { await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(payload)); } catch {}
-}
-
-export async function consumePendingAnsweredCall(): Promise {
- if (inMemoryPending) { const tmp = inMemoryPending; inMemoryPending = null; try { await AsyncStorage.removeItem(STORAGE_KEY); } catch {} return tmp; }
- try { const raw = await AsyncStorage.getItem(STORAGE_KEY); if (raw) { await AsyncStorage.removeItem(STORAGE_KEY); return JSON.parse(raw); } } catch {}
- return null;
-}
-
-export function isPendingStale(p: PendingAnsweredCallPayload, maxAgeMs = 2 * 60 * 1000) {
- return Date.now() - p.storedAt > maxAgeMs;
-}
-```
-
-### 5.5 Wire `App.tsx` for APNs + VoIP token registration and handler init
-
-```tsx lines
-import PushNotificationIOS from "@react-native-community/push-notification-ios";
-import VoipPushNotification from "react-native-voip-push-notification";
-import { voipHandler } from "./VoipNotificationHandler";
-import { consumePendingAnsweredCall, isPendingStale } from "./PendingCallManager";
-
-const APNS_PROVIDER_ID = "YOUR_APNS_PROVIDER_ID";
-
-// Capture APNs device token
-useEffect(() => {
- if (Platform.OS !== "ios") return;
- const onRegister = (deviceToken: string) => { apnsTokenRef.current = deviceToken; };
- PushNotificationIOS.addEventListener("register", onRegister);
- PushNotificationIOS.requestPermissions();
- return () => PushNotificationIOS.removeEventListener("register");
-}, []);
-
-// Capture VoIP token
-useEffect(() => {
- if (Platform.OS !== "ios") return;
- const onVoipRegister = (token: string) => {
- CometChatNotifications.registerPushToken(
- token,
- CometChatNotifications.PushPlatforms.APNS_REACT_NATIVE_VOIP,
- APNS_PROVIDER_ID
- ).catch(err => console.log("[VoIP] register failed", err));
- };
- VoipPushNotification.addEventListener("register", onVoipRegister);
- // token request is triggered in AppDelegate via RNVoipPushNotificationManager.voipRegistration()
- return () => VoipPushNotification.removeEventListener("register");
-}, []);
-
-// After login: register APNs token + init VoIP handler + consume pending accepts
-useEffect(() => {
- const run = async () => {
- if (!loggedIn || Platform.OS !== "ios") return;
- const pending = await consumePendingAnsweredCall();
- if (pending && !isPendingStale(pending)) { await CometChat.acceptCall(pending.sessionId).catch(console.log); }
- const token = apnsTokenRef.current;
- if (token) {
- await CometChatNotifications.registerPushToken(
- token,
- CometChatNotifications.PushPlatforms.APNS_REACT_NATIVE_DEVICE,
- APNS_PROVIDER_ID
- );
- }
- await voipHandler.initialize();
- };
- run();
-}, [loggedIn]);
-```
-
-### 5.6 VoIP push payload (APNs / PushKit)
-Send a VoIP push with `push_type=voip` via APNs using a payload shaped like:
-
-```json
-{
- "aps": { "alert": { "title": "Alice", "body": "Incoming call" }, "content-available": 1 },
- "sessionId": "",
- "callerName": "Alice",
- "handle": "alice",
- "type": "call",
- "uuid": ""
-}
-```
-
-## 6. Handling notification taps and navigation
-
-To handle notification taps and navigate to the appropriate chat screen, you need to set up handlers for both foreground and background notifications.
-
-{/* :TODO: Add code snippets and explanation for setting up Notifee handlers and navigation logic. */}
-
-
-## 7. Badge Count Implementation
-
-CometChat's Enhanced Push Notification payload includes an `unreadMessageCount` field that represents the total number of unread messages across all conversations for the logged-in user. You can use this value to update the app icon badge, providing users with a visual indicator of unread messages.
-
-### 7.1 Enable Unread Badge Count on the CometChat Dashboard
-
-
-
- Go to **CometChat Dashboard → Notifications Engine → Settings → Preferences → Push Notification Preferences**.
-
-
- Scroll down and enable the **Unread Badge Count** toggle.
-
-
-
-Once enabled, CometChat automatically includes the `unreadMessageCount` field in every push payload sent to your app.
-
-### 7.2 Expected Payload Format
-
-CometChat sends APNs payloads with the following structure:
-
-```json
-{
- "aps": {
- "alert": {
- "title": "New Message",
- "body": "John: Hello!"
- },
- "badge": 5,
- "sound": "default"
- },
- "unreadMessageCount": "5",
- "conversationId": "user_abc123"
-}
-```
-
-
-The `aps.badge` field is set server-side by CometChat. iOS automatically updates the app icon badge when the push notification is delivered.
-
-
-### 7.3 Handle Badge Count from Notifications
-
-Update your iOS notification handler to set the badge count programmatically:
-
-```typescript
-import PushNotificationIOS from "@react-native-community/push-notification-ios";
-
-export async function onRemoteNotificationIOS(notification: any) {
- // Extract badge count from push payload
- const data = notification.getData();
- const unreadCount = data?.unreadMessageCount;
-
- if (unreadCount !== undefined && unreadCount !== null) {
- const count = parseInt(unreadCount, 10);
- if (!isNaN(count) && count >= 0) {
- PushNotificationIOS.setApplicationIconBadgeNumber(count);
- console.log("Badge count updated (iOS):", count);
- }
- }
-
- // Handle notification tap
- const isClicked = data?.userInteraction === 1;
- if (isClicked && data?.type === "chat") {
- // Navigate to conversation...
- }
-
- // Required: Notify iOS that processing is complete
- notification.finish(PushNotificationIOS.FetchResult.NoData);
-}
-```
-
-### 7.4 Register Notification Listener
-
-In your `App.tsx`, set up the notification listener:
-
-```typescript
-import PushNotificationIOS from "@react-native-community/push-notification-ios";
-
-useEffect(() => {
- if (Platform.OS === "ios") {
- const onNotification = async (notification: any) => {
- try {
- await onRemoteNotificationIOS(notification);
- } catch (error) {
- console.log("Error in onRemoteNotificationIOS:", error);
- }
- };
-
- PushNotificationIOS.addEventListener("notification", onNotification);
-
- return () => {
- PushNotificationIOS.removeEventListener("notification");
- };
- }
-}, []);
-```
-
-### 7.5 Clear Badge When App Becomes Active
-
-Clear the badge count when the app launches or returns to the foreground:
-
-```typescript
-import { AppState, AppStateStatus, Platform } from "react-native";
-import PushNotificationIOS from "@react-native-community/push-notification-ios";
-
-useEffect(() => {
- const handleAppStateChange = async (nextState: AppStateStatus) => {
- if (nextState === "active" && Platform.OS === "ios") {
- PushNotificationIOS.setApplicationIconBadgeNumber(0);
- console.log("Badge cleared (iOS)");
- }
- };
-
- const subscription = AppState.addEventListener("change", handleAppStateChange);
- return () => subscription.remove();
-}, []);
-```
-
-### 7.6 Clear Badge on Logout
-
-When a user logs out, clear the badge so it doesn't show a stale count on the login screen or for the next user:
-
-```typescript
-import PushNotificationIOS from "@react-native-community/push-notification-ios";
-import { CometChat, CometChatNotifications } from "@cometchat/chat-sdk-react-native";
-
-const handleLogout = async () => {
- // Unregister push token first
- await CometChatNotifications.unregisterPushToken();
-
- // Clear badge before logout
- PushNotificationIOS.setApplicationIconBadgeNumber(0);
-
- // Logout from CometChat
- await CometChat.logout();
- console.log("User logged out, badge cleared");
-};
-```
-
-### 7.7 Clear Badge on Fresh Install / No Logged-In User
-
-On iOS, the badge count may persist after app uninstall and reinstall in certain scenarios. Clear the badge during app initialization when no user is logged in:
-
-```typescript
-import PushNotificationIOS from "@react-native-community/push-notification-ios";
-import { CometChat } from "@cometchat/chat-sdk-react-native";
-
-// During app initialization, after CometChat.init()
-const initializeApp = async () => {
- // Initialize CometChat first
- await CometChatUIKit.init(uiKitSettings);
-
- // Check if user is logged in
- const loggedInUser = await CometChat.getLoggedinUser();
-
- if (!loggedInUser) {
- // No user logged in - clear any stale badge
- PushNotificationIOS.setApplicationIconBadgeNumber(0);
- console.log("No logged-in user, badge cleared");
- }
-};
-```
-
-### 7.8 Clear Badge in Login Listener (Safety Net)
-
-Register a login listener to clear the badge on logout as a backup mechanism:
-
-```typescript
-import PushNotificationIOS from "@react-native-community/push-notification-ios";
-import { CometChat } from "@cometchat/chat-sdk-react-native";
-
-useEffect(() => {
- const listenerID = "BADGE_LOGOUT_LISTENER";
-
- CometChat.addLoginListener(
- listenerID,
- new CometChat.LoginListener({
- logoutOnSuccess: () => {
- // Safety net: clear badge when logout succeeds
- PushNotificationIOS.setApplicationIconBadgeNumber(0);
- console.log("Logout listener: badge cleared");
- },
- })
- );
-
- return () => {
- CometChat.removeLoginListener(listenerID);
- };
-}, []);
-```
-
-### 7.9 Key Implementation Notes
-
-| Consideration | Details |
-| --- | --- |
-| **Backend-driven badge count** | The `unreadMessageCount` value comes directly from CometChat's backend via the push payload, ensuring consistency across all devices. |
-| **iOS server-side badge** | For iOS using APNs, the `aps.badge` field is set server-side by CometChat, so the badge updates automatically even without client-side code. However, you still need to clear it when the app opens. |
-| **Clear on app active** | Always clear the badge when the app becomes active. New notifications will update the badge with the fresh `unreadMessageCount` from the backend. |
-| **Clear on logout** | Always clear the badge when a user logs out to prevent stale counts for the next user. |
-| **Clear on fresh install** | On iOS, the badge count may persist after app reinstall in certain scenarios. Clear the badge during app initialization when no user is logged in. |
-| **Login listener safety net** | Use CometChat's login listener as a backup to ensure badge is cleared on logout. |
-| **Title enhancement** | Optionally display the unread count in the notification title (e.g., "John (5 unread)") for additional visibility. |
-
-### 7.10 Cross-Platform App State Handler
-
-If you're building a cross-platform app, use this combined handler for both iOS and Android:
-
-```typescript
-import { AppState, AppStateStatus, Platform } from "react-native";
-import PushNotificationIOS from "@react-native-community/push-notification-ios";
-import notifee from "@notifee/react-native";
-
-useEffect(() => {
- const handleAppStateChange = async (nextState: AppStateStatus) => {
- if (nextState === "active") {
- // Clear badge for iOS
- if (Platform.OS === "ios") {
- PushNotificationIOS.setApplicationIconBadgeNumber(0);
- console.log("Badge cleared (iOS)");
- }
- // Clear all notifications for Android (also resets badge)
- else if (Platform.OS === "android") {
- await notifee.cancelAllNotifications();
- console.log("Notifications cleared (Android)");
- }
- }
- };
-
- const subscription = AppState.addEventListener("change", handleAppStateChange);
- return () => subscription.remove();
-}, []);
-```
-
-## 8. Testing Checklist
-
-1. Install on a physical iOS device, log in, and verify APNs token registration succeeds.
-2. Send a message from another user:
- - **Foreground:** Banner appears unless that chat is already open.
- - **Background/terminated:** Tap opens the correct conversation; handler runs.
-3. **VoIP:** Send a PushKit VoIP push (payload above); expect CallKit incoming UI; answer and confirm CometChat call connects; end clears the dialer.
-4. Rotate tokens (reinstall or revoke) and confirm `onTokenRefresh` re-registers the new token.
-
-## 9. Troubleshooting
-
-| Symptom | Quick Checks |
-| --- | --- |
-| No pushes | Confirm APNs key uploaded, bundle ID matches, Push extension enabled with correct provider IDs, permissions granted. |
-| Token registration fails | Ensure registration runs **after login**, provider IDs are set, and `registerForRemoteNotifications()` is called. |
-{/* | Notification taps do nothing | Keep foreground/background handlers and ensure navigation ref is ready before routing. | */}
-{/* | Call UI not showing | Verify PushKit VoIP capability, CallKeep entitlements/permissions, and that `voipHandler.initialize()` runs after login. | */}
-{/* | Inline reply needed | Extend Notifee action buttons; CometChat expects you to send the message manually after reading `remoteMessage.data`. | */}
-
----
-
-## Next Steps
-
-
-
-Set up FCM push notifications for Android
-
-
-Strip HTML tags and customize notification content
-
-
-Learn how to send different types of messages
-
-
-Handle incoming messages in real time
-
-
diff --git a/notifications/react-native-push-notifications.mdx b/notifications/react-native-push-notifications.mdx
new file mode 100644
index 000000000..522c620b3
--- /dev/null
+++ b/notifications/react-native-push-notifications.mdx
@@ -0,0 +1,375 @@
+---
+title: "React Native"
+description: "Add CometChat push notifications and VoIP calls to a React Native app (Android + iOS) with the drop-in @cometchat/push-notifications-react-native package."
+---
+
+## What this guide covers
+
+- Adding the `@cometchat/push-notifications-react-native` package and initializing it.
+- Platform wiring: Firebase/`google-services.json` on Android, the setup CLI + PushKit forwarding on iOS.
+- Requesting permission and registering tokens (FCM on Android, APNs + VoIP on iOS) after login.
+- Receiving pushes and letting the package render chat notifications and full-screen / CallKit calls.
+- Handling notification taps (including thread deep-links), incoming-call navigation, and Android OEM permissions.
+- Testing and troubleshooting.
+
+
+The `@cometchat/push-notifications-react-native` package replaces the previous approach of copying the sample app's `notifications` stack and hand-wiring `@react-native-firebase/messaging`, `notifee`, `react-native-callkeep`, and `react-native-voip-push-notification`. Token registration, foreground presentation, notification taps, and the full incoming-call experience (the Android lock-screen call activity and iOS CallKit) are handled inside the package — the design is **JS-first**: native code only shows the UI and captures tokens, while every CometChat action (register token, accept/reject/end call) runs in JavaScript through the Chat SDK your app already ships.
+
+
+## How it works
+
+- **Android (FCM):** Firebase issues the registration token and delivers the CometChat payload as a data message. The package ships its **own** `FirebaseMessagingService`, so it receives the message and shows the notification or full-screen call itself — **you write no FCM handling code**.
+- **iOS (APNs + PushKit):** Apple issues the APNs device token (chat alerts) and the VoIP token (calls). APNs alerts are shown by the system; VoIP pushes are presented through CallKit by the package. Your `AppDelegate` forwards the tokens and incoming VoIP pushes to the package (the setup CLI generates this).
+- **CometChat's role:** The providers you add in the dashboard bind your registered tokens to the logged-in user so CometChat can route pushes on your behalf.
+- **The package's role:** it retrieves the tokens, registers them with CometChat, parses payloads, drives the call UI, and calls the Chat SDK to accept/reject/end. It requires [`@cometchat/chat-sdk-react-native`](https://www.npmjs.com/package/@cometchat/chat-sdk-react-native) as a peer dependency — the one Chat SDK your app already uses, so there is no second SDK to version-align.
+
+## Prerequisites
+
+- The providers, Firebase project, and Apple/APNs credentials from **[Getting Started](/notifications/push-overview)** (this guide assumes those are done).
+- React Native **0.65+**, and an app already initializing and logging in with `@cometchat/chat-sdk-react-native` (or the UI Kit).
+- **Android:** `google-services.json` in `android/app/`, the `com.google.gms.google-services` plugin, `minSdkVersion 24`+.
+- **iOS:** iOS 13.0+ (set the Podfile platform to **14.0** for VoIP/CallKit).
+- A physical device — background delivery, full-screen calls, and VoIP pushes are unreliable on emulators/simulators.
+
+
+**Complete the [Getting Started](/notifications/push-overview) guide first** — enable Push Notifications, add your providers (FCM for Android, APNs + APNs VoIP for iOS), and finish the Firebase/Apple setup. This guide covers only the React Native app wiring.
+
+
+## 1. Store your credentials
+
+Keep the values from Getting Started somewhere your app can read them. Only the fields for the platforms you ship are needed:
+
+```ts lines
+export const AppCredentials = {
+ appId: "YOUR_APP_ID",
+ region: "YOUR_REGION",
+ authKey: "YOUR_AUTH_KEY",
+
+ // Android
+ fcmProviderId: "FCM-PROVIDER-ID",
+
+ // iOS — one provider covers the APNs device token and the VoIP token
+ apnsProviderId: "APNS-PROVIDER-ID",
+};
+```
+
+## 2. Add the package and configure the platform
+
+Install the package (the Chat SDK peer is already in your app):
+
+```bash
+npm install @cometchat/push-notifications-react-native
+# or: yarn add @cometchat/push-notifications-react-native
+```
+
+
+
+ With `google-services.json` already in `android/app/` (from [Getting Started](/notifications/push-overview)):
+
+ 1. Apply the Google Services plugin and Firebase Messaging in your Gradle files:
+
+ ```groovy lines
+ // android/build.gradle
+ buildscript {
+ dependencies {
+ classpath("com.google.gms:google-services:4.4.2")
+ }
+ }
+ ```
+
+ ```groovy lines
+ // android/app/build.gradle
+ apply plugin: "com.google.gms.google-services"
+
+ dependencies {
+ implementation platform("com.google.firebase:firebase-bom:33.16.0")
+ implementation "com.google.firebase:firebase-messaging"
+ }
+ ```
+
+ 2. Keep `minSdkVersion 24` or higher.
+
+
+ You do **not** need to add notification, call, full-screen-intent, or lock-screen permissions to your `AndroidManifest.xml`, and you write **no** FCM/JS message-handling code. The package's library manifest contributes everything it needs — the `FirebaseMessagingService`, the incoming-call foreground service, the full-screen lock-screen `CallRingingActivity`, the notification trampoline/decline receiver, and `POST_NOTIFICATIONS` — and Gradle merges them into your app automatically.
+
+
+
+ 1. Set the deployment target in `ios/Podfile`, then install pods:
+
+ ```ruby
+ platform :ios, '14.0'
+ ```
+
+ ```bash
+ cd ios && pod install && cd ..
+ ```
+
+ 2. Run the setup CLI from your project root — it adds the required `UIBackgroundModes` (`voip`, `remote-notification`, `audio`) and the mic/camera usage strings to `Info.plist`, and generates `ios//CometChatPushNotifications+AppDelegate.swift`:
+
+ ```bash
+ npx cometchat-pn setup
+ ```
+
+ 3. In Xcode, **add the generated `CometChatPushNotifications+AppDelegate.swift` to your app target**, and enable the **Push Notifications** and **Background Modes** capabilities (the latter with *Voice over IP* + *Remote notifications* + *Audio*).
+
+ 4. Forward the PushKit/APNs events to the package from your `AppDelegate`. Create the `PKPushRegistry` on a **background queue** — on a killed-app cold start iOS delivers the incoming push on that queue, and a `.main` queue would sit behind React Native's startup and miss iOS's ~5s "report a call" deadline (iOS then terminates the app with no CallKit UI):
+
+ ```swift lines
+ import PushKit
+
+ // in application(_:didFinishLaunchingWithOptions:)
+ let registry = PKPushRegistry(queue: DispatchQueue(label: "com.cometchat.voip.pushkit"))
+ registry.delegate = self
+ registry.desiredPushTypes = [.voIP]
+
+ // APNs device token (chat/alert pushes)
+ override func application(_ application: UIApplication,
+ didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
+ CometChatPushNotificationsAppDelegate.didRegisterAPNsToken(deviceToken)
+ }
+
+ // VoIP token + incoming VoIP push (PKPushRegistryDelegate)
+ func pushRegistry(_ registry: PKPushRegistry,
+ didUpdate credentials: PKPushCredentials, for type: PKPushType) {
+ CometChatPushNotificationsAppDelegate.didUpdateVoIPToken(credentials.token)
+ }
+ func pushRegistry(_ registry: PKPushRegistry,
+ didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType,
+ completion: @escaping () -> Void) {
+ CometChatPushNotificationsAppDelegate.didReceiveIncomingVoIPPush(payload.dictionaryPayload)
+ completion()
+ }
+ ```
+
+ 5. Verify the wiring at any time:
+
+ ```bash
+ npx cometchat-pn doctor
+ ```
+
+
+
+## 3. Initialize the SDK
+
+Register the killed-state background task **at module scope** in `index.js` (before any component renders), then initialize the package **after the user logs in**.
+
+```js lines
+// index.js
+import { AppRegistry } from 'react-native';
+import { CometChat } from '@cometchat/chat-sdk-react-native';
+import { registerBackgroundCallTask } from '@cometchat/push-notifications-react-native';
+import App from './App';
+import { name as appName } from './app.json';
+
+// Android only: runs when the app is FULLY KILLED and the user taps Decline on the
+// call notification. A killed app has no JS alive, so this headless task boots just
+// enough to reject the call — otherwise the caller only times out. (iOS declines
+// natively via CallKit, so this is a no-op there.)
+registerBackgroundCallTask(async (action, info) => {
+ if (action === 'decline' && info.sessionId) {
+ // (re)initialize + login your CometChat session here, then:
+ await CometChat.rejectCall(info.sessionId, CometChat.CALL_STATUS.REJECTED);
+ }
+});
+
+AppRegistry.registerComponent(appName, () => App);
+```
+
+```ts lines
+// after CometChat.init(...) + login succeeds — e.g. a setupPush() you call on login:
+import {
+ CometChatPushNotifications,
+ CometChatPNHelper,
+} from '@cometchat/push-notifications-react-native';
+
+let subscriptions: Array<() => void> = [];
+
+export async function setupPush() {
+ await CometChatPNHelper.requestNotificationPermission();
+ await CometChatPNHelper.requestCallPermissions(); // mic + camera (Android) before a call connects
+
+ subscriptions.push(
+ CometChatPushNotifications.onNotificationTap(handleTap),
+ CometChatPushNotifications.onCallAccepted(info =>
+ navigate('OngoingCall', { sessionId: info.sessionId, callType: info.callType })),
+ CometChatPushNotifications.onCallEnded(handleCallEnded),
+ CometChatPushNotifications.onMessageReceived(data =>
+ console.log('data push:', data)),
+ );
+
+ await CometChatPushNotifications.init({
+ fcmProviderId: AppCredentials.fcmProviderId, // Android
+ apnsProviderId: AppCredentials.apnsProviderId, // iOS (APNs device + VoIP)
+ // Foreground pushes are rendered in-app (chat UI / WebSocket call overlay),
+ // so suppress the duplicate system banner/CallKit while the app is active:
+ showInForeground: false,
+ });
+}
+```
+
+`init` wires the native events to the Chat SDK, **auto-registers** the device tokens, and drains any cold-start tap/call the app was launched from. It is safe to call again on re-login.
+
+`init` also accepts: `voip` (default `true`), `notificationSmallIcon`, `androidChannelId`, and `androidChannelName`.
+
+## 4. Request permission and register tokens
+
+Permission is requested via `CometChatPNHelper` (call it before pushes/calls arrive):
+
+```ts lines
+await CometChatPNHelper.requestNotificationPermission(); // POST_NOTIFICATIONS (Android 13+) / iOS
+await CometChatPNHelper.requestCallPermissions(); // mic + camera (Android 14+ call FGS)
+// non-prompting check:
+const enabled = await CometChatPNHelper.hasNotificationPermission();
+```
+
+**Token registration is automatic** — `init()` registers the FCM token (Android) and the APNs device + VoIP tokens (iOS) with CometChat, and re-registers on refresh. You rarely need to do it by hand, but you can:
+
+```ts lines
+await CometChatPushNotifications.registerToken('fcm', token); // or 'apns' / 'voip'
+```
+
+**On logout**, unsubscribe your handlers and unregister the token so the device stops receiving pushes for that user:
+
+```ts lines
+export async function teardownPush() {
+ subscriptions.forEach(unsub => unsub());
+ subscriptions = [];
+ await CometChatPushNotifications.unregister();
+}
+```
+
+
+Not unsubscribing on logout leaves the callbacks registered, so a logout → login cycle would fire each handler twice (e.g. navigating to a tapped message twice).
+
+
+## 5. Notification taps and call events
+
+Subscribe once (in `setupPush` above). Each subscribe returns an unsubscribe function.
+
+**Notification tap** — open the conversation, or the **thread** when the push is a thread reply:
+
+```ts lines
+async function handleTap(info) {
+ // info: { receiverType, sender, receiver, conversationId, messageId, parentMessageId, senderName }
+ if (info.parentMessageId) {
+ const parent = await CometChat.getMessageDetails(info.parentMessageId);
+ navigate('ThreadView', { message: parent, highlightMessageId: info.messageId });
+ return;
+ }
+ navigateToConversation({
+ receiverType: info.receiverType,
+ sender: info.sender,
+ conversationId: info.conversationId,
+ });
+}
+```
+
+**Call accepted** — the package has *already* accepted the call via the Chat SDK; just open your call screen:
+
+```ts lines
+CometChatPushNotifications.onCallAccepted(info => {
+ navigate('OngoingCall', { sessionId: info.sessionId, callType: info.callType });
+});
+```
+
+**Call ended** — a ringing call was cancelled/declined/ended, **or** the user ended the call from the iOS CallKit UI. The Calls SDK's own listener doesn't see a CallKit-initiated end, so tear the call down here:
+
+```ts lines
+import { CometChatCalls } from '@cometchat/calls-sdk-react-native';
+
+function handleCallEnded(info) {
+ if (info.sessionId) CometChat.endCall(info.sessionId).catch(() => {});
+ try { CometChatCalls.endSession(); } catch {}
+ CometChat.clearActiveCall?.();
+ // leave the ongoing-call screen if you're on it
+}
+```
+
+
+**Cold-start VoIP handling (iOS):** when the app is killed and a VoIP push arrives, the package presents CallKit natively via PushKit before React Native is ready. When the user answers, the app cold-starts, `init()` replays the accepted call, `onCallAccepted` fires, and the package has already called `CometChat.acceptCall` — set up your call session/screen there. (This path is why the `PKPushRegistry` must be on a background queue — see step 2.)
+
+
+## 6. Android: OEM permissions for lock-screen calls
+
+The package declares the standard permissions and uses the correct `setShowWhenLocked` / `setTurnScreenOn` flags, so full-screen calls over the lock screen work out of the box on stock Android (including Android 14+). **OEM skins (MIUI/Redmi/POCO, Oppo, Vivo) additionally gate background-launched full-screen activities** behind their own toggles — without them, a locked/killed call shows only a heads-up notification (with ringtone), and the full-screen screen appears only after unlock.
+
+Guide users to grant, on those devices:
+
+- **Autostart** — Settings → Apps → *your app* → Autostart (or the Security app).
+- **Display pop-up windows while running in background** — Settings → Apps → *your app* → Other permissions.
+- **Show on lock screen** — same "Other permissions" screen.
+- Disable **battery optimization** for the app.
+
+These OEM settings cannot be granted programmatically (the OS blocks it); open the app's settings page so the user can toggle them:
+
+```ts lines
+import { Linking, Platform } from 'react-native';
+if (Platform.OS === 'android') Linking.openSettings();
+```
+
+## 7. Badge count
+
+CometChat's Enhanced Push payload includes an `unreadMessageCount` field (total unread across conversations).
+
+
+
+ With APNs the badge is handled **server-side**: CometChat sets `aps.badge` in the payload and iOS updates the app icon automatically — no client code required.
+
+
+ Android has no OS-level app-icon badge API. If you want a launcher badge, read `unreadMessageCount` from the payload in `onMessageReceived` and apply it with your own badge library — the push package does not manage launcher badges.
+
+ ```ts lines
+ CometChatPushNotifications.onMessageReceived(data => {
+ const count = Number(data.unreadMessageCount ?? 0);
+ // hand `count` to your badge library
+ });
+ ```
+
+
+
+## 8. Testing checklist
+
+1. Run on a physical device. Grant notification, microphone, and camera permissions when prompted (Android 13+ requires `POST_NOTIFICATIONS`).
+2. Send a message from another user:
+ - Foreground: no system banner (with `showInForeground: false`); your in-app UI shows it.
+ - Background: a notification appears; tapping opens the right conversation via `onNotificationTap` (and the thread, for a thread reply).
+3. Force-quit the app, send another message, tap the notification, and confirm it cold-starts to the conversation.
+4. Trigger an incoming CometChat call and confirm:
+ - The full-screen call UI (Android) / CallKit (iOS) shows the caller with Accept/Decline, even on the lock screen.
+ - **Accept** joins the call (audio works both ways) and the screen tears down when the call ends.
+ - **Decline** rejects the call promptly on the caller side — including from a killed state.
+ - **Caller cancels** while it's ringing → the callee ring dismisses.
+5. On an OEM device (MIUI/Oppo/Vivo), grant the section-6 permissions and re-check locked/killed calls.
+
+## 9. Troubleshooting
+
+| Symptom | Platform | Quick checks |
+| --- | --- | --- |
+| No notifications received | Android | Confirm `google-services.json` is in `android/app/`, the package name matches Firebase, `firebase-messaging` + the `google-services` plugin are applied, and `POST_NOTIFICATIONS` is granted (Android 13+). |
+| Killed app doesn't ring for a VoIP push | iOS | Ensure the `PKPushRegistry` is created on a **background queue** and the `AppDelegate` forwards `didReceiveIncomingVoIPPush` to `CometChatPushNotificationsAppDelegate`. Run `npx cometchat-pn doctor`. |
+| Accepted call connects but has no audio | iOS | Confirm the `audio` background mode is present (the setup CLI adds it) and `react-native-webrtc` (via the Calls SDK) is linked in the app — the package coordinates CallKit's audio session with WebRTC automatically. |
+| Full-screen call UI not showing on lock screen | Android | OEM gate — grant Autostart / "Display pop-up while running in background" / "Show on lock screen" and disable battery optimization (section 6). |
+| Declining a killed-state call doesn't reject the caller | Both | Android: ensure `registerBackgroundCallTask` is registered at module scope in `index.js`. iOS: the package handles it via CallKit — verify `AppDelegate` forwarding. |
+| Foreground call shows twice (in-app + CallKit/banner) | Both | Set `showInForeground: false` in `init()` so foreground calls use your in-app UI only. |
+| Duplicate navigation after re-login | Both | Unsubscribe every handler and call `unregister()` on logout (step 4). |
+| No VoIP pushes | iOS | Ensure Push Notifications + Background Modes (Voice over IP) are enabled, `aps-environment` is correct (`production` for release), the bundle ID matches the CometChat APNs VoIP provider, and the VoIP cert is uploaded to the dashboard. |
+| Token registration errors | Both | Verify the provider IDs match the dashboard exactly and that `init()` runs **after** login. |
+
+## Resources
+
+
+
+ The drop-in push & VoIP package on npm.
+
+
+ The peer Chat SDK the package registers tokens and drives calls through.
+
+
diff --git a/sdk/react-native/push-notification-html-stripping.mdx b/sdk/react-native/push-notification-html-stripping.mdx
index 8e8ccdc6a..eef10604c 100644
--- a/sdk/react-native/push-notification-html-stripping.mdx
+++ b/sdk/react-native/push-notification-html-stripping.mdx
@@ -243,11 +243,8 @@ If your project uses React Native Firebase (e.g., for FCM on Android), you may a
## Next Steps
-
-Set up FCM push notifications for Android
-
-
-Set up APNs push notifications for iOS
+
+Set up FCM (Android) and APNs/VoIP (iOS) push notifications
Learn how to send different types of messages