@cometchat/push-notifications-react-native
v1.0.0
Published
Drop-in push notifications and VoIP calling for CometChat React Native apps. JS-first: a native display engine (FCM/notifications + CallKit/PushKit) drives the OS UI; all CometChat actions run through your existing chat-sdk-react-native — no second native
Readme
CometChat Push Notifications — React Native
Drop-in push notifications and VoIP calling for CometChat React Native apps.
JS-first design. A small native "display engine" (our own Kotlin + Swift)
shows the notification and rings the system call UI (FCM + notifications on
Android; PushKit + CallKit on iOS) and captures push tokens. Every CometChat
action — registering the token, accepting/rejecting a call — runs in JavaScript
through the @cometchat/chat-sdk-react-native your app already ships. There is
no second, native Chat SDK, so there is nothing to version-align (the same
approach the CometChat Flutter plugin uses).
No @react-native-firebase, Notifee, CallKeep, or voip-push in this package. The
only native dependency is firebase-messaging (the FCM transport on Android). Already
using @react-native-firebase/messaging? See
Coexisting with another FCM service.
Installation
npm install @cometchat/push-notifications-react-native
cd ios && pod installRequires React Native 0.78 or later and @cometchat/chat-sdk-react-native 4.0.10 or later
(already in your app), used at runtime for all CometChat calls. Apps with calls also need
@cometchat/calls-sdk-react-native, an optional peer.
One-time native setup
Android needs no app-side native code — the library ships its own FCM service
and call UI. You only add google-services.json and apply the
com.google.gms.google-services plugin (standard FCM setup). Before publishing, read
Android: calls, permissions and Google Play.
iOS needs a few lines in your app:
Enable the Push Notifications capability, and Background Modes with Voice over IP, Remote notifications and Audio.
Add
NSMicrophoneUsageDescriptionandNSCameraUsageDescriptiontoInfo.plist.In
AppDelegate, start the package's PushKit registry before React Native starts, and forward the APNs token:import react_native_cometchat_push_notifications // in application(_:didFinishLaunchingWithOptions:), before factory.startReactNative(...) CometChatPushNotificationsAppDelegate.registerForVoIPPushes() func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { CometChatPushNotificationsAppDelegate.didRegisterAPNsToken(deviceToken) }The package owns PushKit, so don't create a
PKPushRegistryof your own. If another library already owns one, skipregisterForVoIPPushes()and forward that registry's token and pushes todidUpdateVoIPToken/didReceiveIncomingVoIPPushinstead.Chat-only apps skip
registerForVoIPPushes()and the Voice over IP and Audio background modes, and passvoip: falsetoinit().
The full AppDelegate code is in the
React Native push notifications guide for iOS.
Usage
In index.js, at module scope (never inside a component), so a fully killed Android app
can reject a call declined from its notification — without it the caller keeps ringing:
import { registerBackgroundCallTask } from "@cometchat/push-notifications-react-native";
registerBackgroundCallTask();Then in your app:
import {
CometChatPNHelper,
CometChatPushNotifications,
} from "@cometchat/push-notifications-react-native";
// 1. after every login (and a session restored on launch) — AFTER CometChat.init(...).
// Ask for permissions first: without the notification permission, Android 13+ shows
// no notifications at all. A rejection means the OS couldn't ask; init() still runs.
await CometChatPNHelper.requestNotificationPermission().catch(() => false);
await CometChatPNHelper.requestCallPermissions(); // Android mic + camera, before a call connects
// Tokens register automatically, for this login session.
await CometChatPushNotifications.init({
fcmProviderId: "your-fcm-provider",
apnsProviderId: "your-apns-provider",
// ringInForeground: false — only if your app shows its own incoming-call screen while
// it is open (the CometChat UI Kit does). By default calls ring with CallKit / the
// Android ringing screen in every app state.
});
// 2. navigate when a chat notification is tapped
CometChatPushNotifications.onNotificationTap(({ sender, receiver, receiverType, parentMessageId }) => {
// resolve User/Group via the Chat SDK and navigate
});
// 3. a call answered from the system UI is already accepted (CometChat.acceptCall
// ran for you) — just open your ongoing-call screen
CometChatPushNotifications.onCallAccepted(({ sessionId, callType }) => {
navigate("OngoingCall", { sessionId, callType });
});
// 4. log out: unregister FIRST — it needs the session, so after logout it fails and the
// device keeps receiving this user's notifications
async function logout(): Promise<boolean> {
try {
await CometChatPushNotifications.unregister();
} catch {
return false; // e.g. offline — stay logged in so the user can retry
}
await CometChat.logout();
return true;
}That's it — no token plumbing. The native engine hands the token to JS and the
package calls CometChatNotifications.registerPushToken(...) on your Chat SDK.
API
| Method / event | Purpose |
| --- | --- |
| init(config) | Wire the native display engine + auto-register tokens, after each login. Options: provider IDs, voip, ringInForeground, showInForeground, Android icon and channel. |
| onNotificationTap(cb) | User tapped a chat notification. cb returns an unsubscribe fn. |
| onCallAccepted(cb) | A call was answered and accepted via the Chat SDK — open your call screen. |
| onCallEnded(cb) | A ringing call was cancelled / declined / ended, or rang out unanswered (45s). |
| onMessageReceived(cb) | A data push arrived (e.g. to markAsDelivered via the Chat SDK). |
| registerToken(platform, token) | Manual token registration (rarely needed). |
| unregister() | Unregister this device's token. Call it before logout — it needs the session. |
| dispose() | Detach the native listeners (init() does this itself before re-wiring). |
| registerBackgroundCallTask(handler?) | Android: lets a killed app reject a declined call. Module scope in index.js. |
| CometChatPNHelper.isCometChatNotification(data) | Route only CometChat pushes. |
| CometChatPNHelper.requestNotificationPermission() / hasNotificationPermission() | OS notification permission. |
| CometChatPNHelper.requestCallPermissions() | Android microphone + camera, needed before a call connects (no-op on iOS). |
Android: calls, permissions and Google Play
The package's manifest adds what calls need to your app: USE_FULL_SCREEN_INTENT,
FOREGROUND_SERVICE_PHONE_CALL, MANAGE_OWN_CALLS, RECORD_AUDIO, CAMERA and
BLUETOOTH_CONNECT, plus the incoming-call service and ringing screen. Camera and
microphone hardware are declared optional, so Google Play doesn't hide the app from
devices without them.
Apps with calls. For apps targeting Android 14+, Google Play requires two declarations in Play Console under App content (Play Console Help):
- Full-screen intent permission — declare calling as core functionality. Without it
Play revokes
USE_FULL_SCREEN_INTENT, and calls ring only as a heads-up notification (the package logs a warning when that happens). - Foreground service permissions — declare the Phone call type.
Chat-only apps. Pass voip: false to init() so call pushes never ring, skip
requestCallPermissions() and registerBackgroundCallTask(), and remove the call
permissions and components in your app's android/app/src/main/AndroidManifest.xml (with
xmlns:tools="http://schemas.android.com/tools" on <manifest>):
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" tools:node="remove" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" tools:node="remove" />
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" tools:node="remove" />
<uses-permission android:name="android.permission.RECORD_AUDIO" tools:node="remove" />
<uses-permission android:name="android.permission.CAMERA" tools:node="remove" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" tools:node="remove" />
<application>
<service android:name="com.cometchat.pushnotification.reactnative.IncomingCallService" tools:node="remove" />
<activity android:name="com.cometchat.pushnotification.reactnative.CallRingingActivity" tools:node="remove" />
</application>tools:node="remove" drops an entry whichever library declared it — keep any permission
another part of your app still uses.
Coexisting with another FCM service
Android delivers FCM messages and token refreshes to only one
FirebaseMessagingService. If your app has another — its own, or the one in
@react-native-firebase/messaging — it is undefined which one receives them, and the
other silently gets nothing. Replace both with one service of your own that forwards to
each:
In
android/app/src/main/AndroidManifest.xml:<application> <service android:name="com.cometchat.pushnotification.reactnative.CometChatFcmService" tools:node="remove" /> <service android:name="io.invertase.firebase.messaging.ReactNativeFirebaseMessagingService" tools:node="remove" /> <service android:name=".AppMessagingService" android:exported="false"> <intent-filter> <action android:name="com.google.firebase.MESSAGING_EVENT" /> </intent-filter> </service> </application>In
android/app/build.gradle, add Firebase Messaging so your service can extend it (use the BOM version your other Firebase libraries use):dependencies { implementation platform("com.google.firebase:firebase-bom:33.16.0") implementation "com.google.firebase:firebase-messaging" }Add the service next to
MainApplication.kt:package com.yourapp // your app's package import com.cometchat.pushnotification.reactnative.CometChatFcmService import com.google.firebase.messaging.RemoteMessage import io.invertase.firebase.messaging.ReactNativeFirebaseMessagingService class AppMessagingService : ReactNativeFirebaseMessagingService() { override fun onMessageReceived(message: RemoteMessage) { // CometChat's pushes are shown by this package; anything else goes on. if (!CometChatFcmService.handleMessage(this, message)) super.onMessageReceived(message) } override fun onNewToken(token: String) { CometChatFcmService.handleNewToken(this, token) super.onNewToken(token) } }Without React Native Firebase, extend
FirebaseMessagingServiceand run your own handling where this callssuper.React Native Firebase also hands every push to its JavaScript handlers. Skip CometChat's there, in
onMessageandsetBackgroundMessageHandler:messaging().onMessage(async (remoteMessage) => { if (CometChatPNHelper.isCometChatNotification(remoteMessage.data)) return; // shown by this package // your handling });
How it works
Push arrives ─▶ native display engine (Kotlin / Swift)
• shows notification / rings CallKit ← OS-level, must be native
• captures FCM / APNs / VoIP token
• emits an event to JS
JS (this package) ─▶ your @cometchat/chat-sdk-react-native
• registerPushToken(token, platform, providerId)
• acceptCall(sessionId) / rejectCall(sessionId)The native side never touches the CometChat SDK; JavaScript does all of it, through the one SDK instance your app is already logged into.
Documentation
Full setup for Android and iOS, notification taps, calls, logout and troubleshooting: React Native push notifications guides for Android and iOS.
License
See LICENSE.md.
