react-native-mehery-event-sender
v0.0.49
Published
A React Native SDK for event tracking that works with both Expo Go and bare React Native applications
Readme
react-native-mehery-event-sender
React Native SDK for push notifications, in-app notifications, polls, and event tracking.
What your app must add (quick checklist)
Your example/consumer app must include all of the following:
- Firebase config files:
- Android:
android/app/google-services.json - iOS:
ios/GoogleService-Info.plist
- Android:
- Google Services Gradle plugins on Android (
com.google.gms.google-services) @react-native-firebase/app+@react-native-firebase/messaging@react-native-async-storage/async-storagereact-native-push-notification- App credentials in native config — three pairs (prod, sandbox, dev) matching your
initSdkenvironment readCredentialsForEnvironment(environment)+pushappAuth(xApiId, xApiKey)on every app launch beforeinitSdk- SDK initialization in app startup (
initSdk) PollOverlayProvidermounted once at app root- iOS background mode for remote notifications (
remote-notificationinInfo.plist)
Installation
npm install react-native-mehery-event-sender
npm install @react-native-firebase/app @react-native-firebase/messaging
npm install @react-native-async-storage/async-storage react-native-push-notificationThen for iOS:
cd ios && pod installStep-by-step setup for example app
1) Add Firebase files
- Place
google-services.jsoninandroid/app/ - Place
GoogleService-Info.plistin your iOS app target
2) Android Gradle setup
In android/build.gradle, add:
buildscript {
dependencies {
classpath 'com.google.gms:google-services:4.3.15'
}
}In android/app/build.gradle, add:
apply plugin: 'com.google.gms.google-services'3) Android manifest permissions
Make sure your app has notification/network permissions in AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />3a) Android: one Firebase messaging service (required for background rich notifications)
@react-native-firebase/messaging registers ReactNativeFirebaseMessagingService with an empty onMessageReceived. This SDK provides com.meheryeventsender.MyFirebaseMessagingService (subclass) for BigPicture, CTAs, and rich layout. If both services stay in the merged manifest, only one may receive FCM, and you can get no custom images or action buttons in the background.
Add xmlns:tools on the root <manifest> if needed, then inside <application> remove the default RNFB service so only the Mehery service remains:
<service
android:name="io.invertase.firebase.messaging.ReactNativeFirebaseMessagingService"
android:exported="false"
tools:node="remove" />3b) Android: native FCM logs (logcat)
The SDK logs FCM handling at INFO from MyFirebaseMessagingService (not Debug), so default logcat filters that hide Log.d still show them.
# Quote '*:S' on zsh (otherwise the shell treats * as a glob and errors).
adb logcat '*:S' 'MyFirebaseMessagingService:I'Simpler (shows only that tag at Info+; also safe in zsh):
adb logcat -s MyFirebaseMessagingService:IIf you see no lines tagged MyFirebaseMessagingService when a push arrives in the background, onMessageReceived did not run for that delivery. Common causes: a top-level FCM notification payload while the app is backgrounded (the OS may show a stock notification and not call your service), or a missing tools:node="remove" so the wrong FirebaseMessagingService handles the message. Use data-only, high-priority FCM for Android and confirm the manifest step in 3a. Also look for Mehery FCM: onNewToken after install/token refresh; that proves this service class is active.
4) iOS capabilities
In your iOS Info.plist, include:
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>Also enable Push Notifications capability in Xcode for your app target.
4a) App credentials (required)
Store three PushApp credential pairs in native config — one per initSdk environment. Use the same environment variable to pick credentials and to call initSdk so API host and auth always match.
| initSdk env | Host | iOS keys | Android strings |
| --- | --- | --- | --- |
| false | pushapp.ai | MeheryProdAppId / MeheryProdAppKey | mehery_prod_app_id / mehery_prod_app_key |
| true | pushapp.net.in | MeherySandboxAppId / MeherySandboxAppKey | mehery_sandbox_app_id / mehery_sandbox_app_key |
| 'development' | pushapp.co.in | MeheryDevAppId / MeheryDevAppKey | mehery_dev_app_id / mehery_dev_app_key |
Legacy single-key names (MeheryAppId / MeheryAppSecretKey, mehery_app_id / mehery_app_secret_key) still work as a production fallback only.
iOS — add to your app target Info.plist:
<key>MeheryProdAppId</key>
<string>pa_your_prod_app_id</string>
<key>MeheryProdAppKey</key>
<string>pas_your_prod_app_key</string>
<key>MeherySandboxAppId</key>
<string>pa_your_sandbox_app_id</string>
<key>MeherySandboxAppKey</key>
<string>pas_your_sandbox_app_key</string>
<key>MeheryDevAppId</key>
<string>pa_your_dev_app_id</string>
<key>MeheryDevAppKey</key>
<string>pas_your_dev_app_key</string>Android — add to android/app/src/main/res/values/strings.xml:
<string name="mehery_prod_app_id" translatable="false">pa_your_prod_app_id</string>
<string name="mehery_prod_app_key" translatable="false">pas_your_prod_app_key</string>
<string name="mehery_sandbox_app_id" translatable="false">pa_your_sandbox_app_id</string>
<string name="mehery_sandbox_app_key" translatable="false">pas_your_sandbox_app_key</string>
<string name="mehery_dev_app_id" translatable="false">pa_your_dev_app_id</string>
<string name="mehery_dev_app_key" translatable="false">pas_your_dev_app_key</string>Rebuild the native app after changing these values.
Do not generate new API keys unless you must. Credentials are stored in native config (
Info.plist/strings.xml), not fetched at runtime from a server. If you create a newpa_/pas_pair in the PushApp dashboard, you must update the matching native values and ship a new app release — existing installs will keep using the old credentials until users update. Prefer keeping your current keys; only rotate when required for security.
Match credentials to environment. If you pass sandbox credentials while
initSdk(..., false)points at production, requests will hit the wrong host with the wrong app identity.
5) Authenticate and initialize SDK in app startup
On every cold start, read credentials for your chosen environment, call pushappAuth, then initSdk with the same environment. Every API request then includes x-api-id and x-api-key headers automatically.
import { useEffect } from 'react';
import {
pushappAuth,
initSdk,
readCredentialsForEnvironment,
type SdkInitEnvironmentParam,
} from 'react-native-mehery-event-sender';
useEffect(() => {
const bootstrap = async () => {
const environment: SdkInitEnvironmentParam = false; // production
const { xApiId, xApiKey } =
await readCredentialsForEnvironment(environment);
await pushappAuth(xApiId, xApiKey);
await initSdk(null, 'your_tenant_your_channel_id', environment);
};
bootstrap();
}, []);Optional manual override (e.g. testing): await pushappAuth('pa_your_app_id', 'pas_your_app_key') without reading native config.
pushappAuth() with no arguments reads production credentials only (legacy / single-env apps).
The third argument to initSdk selects which pushapp host the SDK uses for API and WebSocket calls ({tenant}.pushapp.…):
false— production:pushapp.aitrue— sandbox:pushapp.net.in(default)'development'— development:pushapp.co.in
The fourth argument controls SDK console logging:
true— SDK debug output is printed to the JS console (default)false— all SDK-ownedconsole.log/warn/errorcalls are suppressed
Native Android FCM logcat lines are unaffected (native code).
import { useEffect } from 'react';
import {
pushappAuth,
initSdk,
readCredentialsForEnvironment,
type SdkInitEnvironmentParam,
} from 'react-native-mehery-event-sender';
useEffect(() => {
const bootstrap = async () => {
const environment: SdkInitEnvironmentParam = false;
const { xApiId, xApiKey } =
await readCredentialsForEnvironment(environment);
await pushappAuth(xApiId, xApiKey);
// identifier format: "<tenant>_<channel>"
await initSdk(null, 'your_tenant_your_channel_id', environment);
// await initSdk(null, 'your_tenant_your_channel_id', true); // sandbox
// await initSdk(null, 'your_tenant_your_channel_id', 'development'); // development
};
bootstrap();
}, []);initSdk is async — always await it so device registration and WebSocket connect finish before you rely on SDK state.
Optional geo context before init (used by register and event payloads):
import { setGeoIP } from 'react-native-mehery-event-sender';
setGeoIP({
ip: '203.0.113.1',
location: { lat: 19.076, lng: 72.8777 },
country: { iso_code: 'IN', name: 'India' },
});Wait for init to complete before login if needed:
import { waitForSdkReady } from 'react-native-mehery-event-sender';
await waitForSdkReady();
await OnUserLogin('user_123');6) Mount poll overlay once at root
import { PollOverlayProvider } from 'react-native-mehery-event-sender';
export default function App() {
return (
<>
{/* your app routes/screens */}
<PollOverlayProvider />
</>
);
}7) Link user and page/session events
import {
OnUserLogin,
OnUserLogOut,
OnPageOpen,
sendCustomEvent,
updateUserProfile,
} from 'react-native-mehery-event-sender';Call each event where it best matches the user journey:
a) User login event
// Call after successful sign-in/signup to map this device/session to your user ID
await OnUserLogin('user_123');b) Profile update
// Call after login when you have customer fields or cohorts to sync.
// Uses `user_id` and channel from storage (set by init + login), PUTs `/v1/customer/profile`.
// The SDK stores the last successful push locally and only calls the API when profile data changed.
await updateUserProfile(
{ name: 'Jane Doe', email: '[email protected]', city: 'Mumbai' },
{ segment: 'trial', plan: 'free' }
);c) Page open event
// Call when a screen/page is shown (use your route/screen name)
OnPageOpen('home');d) Custom event
// Call for user actions you want to track with extra metadata.
// You can send any custom keys that match your analytics/business needs.
sendCustomEvent('login_clicked', {
source: 'welcome_screen',
method: 'google',
campaign_id: 'spring_launch_2026', // custom key
button_variant: 'primary', // custom key
plan_type: 'trial', // custom key
});e) User logout event
// Call before/after clearing local auth state when user signs out
await OnUserLogOut('user_123');8) (Optional) Render in-app poll placeholders
import {
InlinePollContainer,
TooltipPollContainer,
} from 'react-native-mehery-event-sender';Use:
InlinePollContainerwhere inline poll cards should render.TooltipPollContaineraround UI elements that should receive tooltip polls.
Public API reference
| API | Description |
| --- | --- |
| pushappAuth(appId?, appSecretKey?) | Persist credentials locally, inject x-api-id / x-api-key headers on every API call |
| readCredentialsForEnvironment(environment) | Read prod / sandbox / dev credentials from native config for the given initSdk environment |
| readNativeAppCredentials() | Read production credentials only (legacy / single-env) |
| initSdk(context, identifier, environment?, logs?) | Initialize SDK; identifier format "<tenant>_<channel>" |
| waitForSdkReady() | Resolves when initSdk has finished |
| setGeoIP(geo) | Set geo context for register/events |
| OnUserLogin(userId) / OnUserLogOut(userId) | Link or delink device to host user |
| OnPageOpen(name) / OnPageClose(name) | Screen lifecycle events |
| OnAppOpen() / OnAppLaunch() | App lifecycle events |
| sendCustomEvent(name, data?, options?) | Custom business events |
| updateUserProfile(additionalInfo, cohorts?, options?) | Sync profile to PushApp |
| updatePushToken(token?) | Refresh FCM/APNs token with backend |
| setDeviceMetadata(headers) | Merge extra string headers into every request |
| getDeviceId() | Persistent SDK device UUID |
| PollOverlayProvider, showPollOverlay, hidePollOverlay | Poll overlay UI |
| InlinePollContainer, TooltipPollContainer | Inline / tooltip poll placeholders |
| registerFcmBackgroundHandler() | Register background FCM handler early in index.js |
| ensureAndroidNotificationPermission() | Request POST_NOTIFICATIONS on Android 13+ |
| setNotificationUrlHandler, configureNotificationLinkRewrites, openNotificationLink | Notification deep-link helpers |
| triggerCarouselNotification, triggerLiveActivity | Local test helpers for rich push / Live Activity |
Types exported: SdkInitEnvironmentParam, GeoIpInput, GeoIpPayload, OnUserLoginResult, SendCustomEventOptions, UpdateUserProfileOptions, UpdateUserProfileResult, TriggerCarouselNotificationParams, NotificationLinkRewrite, FcmBackgroundMessageListener.
Example app: iOS notification extensions (reference)
The example/ios project ships with native targets you can use as a starting point when wiring push, rich notifications, and Live Activities. Copy or adapt the Swift, plists, and assets into your own app; branding and UI design are yours—these paths only show where the hooks and data live.
| Area | Path |
| -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Rich notification UI (content extension) | example/ios/ImagePreviewExtension/NotificationViewController.swift (and MainInterface.storyboard, Assets.xcassets in the same folder) |
| Modify notification content before display (service extension) | example/ios/ImageServiceExtension/NotificationService.swift |
| Live Activity / delivery-style widget data and UI | example/ios/DeliveryActivity/ (Swift sources, Info.plist, Assets.xcassets) |
Mirror the same targets and capabilities in Xcode on your app if you are not using the example workspace directly.
Notification payload notes
Android: FCM data-only for rich background notifications
Foreground vs background use different code paths. When the app is in the foreground, JavaScript (messaging().onMessage) can show a local notification with big-picture image and CTA actions from the payload. When the app is in the background (or not running), only the native FirebaseMessagingService (MyFirebaseMessagingService) can add BigPicture, custom layouts, and NotificationCompat actions.
On Android, Firebase will not call onMessageReceived for messages that use a top-level FCM notification block while the app is in the background; the system shows a default tray notification instead, which does not include this SDK’s custom images or action buttons. To get rich background notifications, send data-only messages: put everything the app needs (title, body, image / imageUrl / image_url, cta_buttons or title1 / url1, etc.) as string fields in the FCM data map, and set high priority for the Android message (FCM HTTP v1: e.g. android.priority: HIGH) so delivery is not deferred too long under Doze.
If you need a notification payload for iOS or other platforms, use per-platform FCM overrides (e.g. data-only for Android, notification + APNs for iOS). The setBackgroundMessageHandler in JS does not replace this: it cannot make onMessageReceived run when the system does not deliver the message to your service.
How to verify: With the app in the background, send a data-only test push, then adb logcat -s MyFirebaseMessagingService:I (or adb logcat '*:S' 'MyFirebaseMessagingService:I') — you should see Mehery FCM: onMessageReceived and FCM[raw] / FCM[merged] lines. With a notification block, the service often will not log in background, and the tray will show a basic system notification. Compare with the same content sent as data-only to confirm images and CTA actions.
Android image keys
- Single image:
image,imageUrl,image_url - Carousel:
imageUrls,image_urls,carousel_images, orimage1,image2, ...
Body tap opens a link (notification_url) — Android and iOS
When the push payload includes notification_url (or notificationUrl), tapping the notification body (title/message area) opens that URL in the system browser. CTA action buttons still use their own URLs (cta_buttons / url1–url3 on Android; buttons array / action IDs on iOS). Without notification_url, body tap only launches the app (unchanged).
The SDK resolves the body URL from the root payload and from Mehery template nests: style.notification_url, stringified style, templateData, and template.style / template.data (same shape as the template API style object).
| Platform | Payload field | Body tap handler |
| -------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| Android | FCM data.notification_url (or nested style / templateData) | Native NotificationCtaUrlActivity + foreground JS Linking.openURL |
| iOS | APNS / FCM data (root or nested style / templateData) | Example AppDelegate.swift urlForNotificationBody + JS Linking.openURL fallback |
Push server requirement: /send-notification must include notification_url on the device payload (flattened in FCM data is best). If it only exists in the template style object, the send step must still forward that object (or flatten it) so iOS/Android can read it on tap. Mapping only buttons → url1 / title1 / action1 is not enough.
iOS Live Activity: The example app only starts a Live Activity when activity_id is set and the payload looks like a live template (message1–message3, progressPercent, live_activity=true, or a hero image URL). Simple templates with only activity_id no longer show an empty Live Activity before the banner.
Example payload after correct server mapping:
{
"title": "Hello",
"body": "World",
"notification_url": "https://example.com/article",
"url1": "https://mehery.com",
"title1": "Yes",
"action1": "PUSHAPP_YES"
}iOS host apps: The npm package does not ship AppDelegate — merge urlForNotificationBody and the default-action URL open from the example app into your target’s AppDelegate (see Example app: iOS notification extensions).
iOS action category example
To show 3 action buttons, send category THREE_BUTTON_CATEGORY in APNs payload.
Action IDs received in JS/native tap handling are:
PUSHAPP_ACTION_1PUSHAPP_ACTION_2PUSHAPP_ACTION_3
ProGuard
-keep class com.mehery.pushapp.** { *; }Support
Raise issues or feature requests in GitHub Issues.
