npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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
  • Google Services Gradle plugins on Android (com.google.gms.google-services)
  • @react-native-firebase/app + @react-native-firebase/messaging
  • @react-native-async-storage/async-storage
  • react-native-push-notification
  • App credentials in native config — three pairs (prod, sandbox, dev) matching your initSdk environment
  • readCredentialsForEnvironment(environment) + pushappAuth(xApiId, xApiKey) on every app launch before initSdk
  • SDK initialization in app startup (initSdk)
  • PollOverlayProvider mounted once at app root
  • iOS background mode for remote notifications (remote-notification in Info.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-notification

Then for iOS:

cd ios && pod install

Step-by-step setup for example app

1) Add Firebase files

  • Place google-services.json in android/app/
  • Place GoogleService-Info.plist in 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:I

If 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 new pa_ / 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.ai
  • true — 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-owned console.log / warn / error calls 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:

  • InlinePollContainer where inline poll cards should render.
  • TooltipPollContainer around 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, or image1, 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 / url1url3 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 buttonsurl1 / 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 (message1message3, 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_1
  • PUSHAPP_ACTION_2
  • PUSHAPP_ACTION_3

ProGuard

-keep class com.mehery.pushapp.** { *; }

Support

Raise issues or feature requests in GitHub Issues.