Sign In

@circlehq/push-react-native

Package Overview
Dependencies
Maintainers
2
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@circlehq/push-react-native

CircleHQ Push React Native SDK — register device push tokens, identify users, and report notification events.

latest
npmnpm
Version
1.4.3
Version published
Maintainers
2
Created
Source

@circlehq/push-react-native

CircleHQ Push SDK for React Native. Register device tokens, identify users, display notifications, and track engagement events — all backed by Circle's infrastructure. No Firebase project setup required on your end.

Features

  • Bring your own credentials (BYOC) — upload your Firebase Service Account once on the Circle dashboard; the SDK fetches your config at build time using only your Circle API key. No Firebase project setup in your app code.
  • One-call identify — pass an email or phone number; the SDK handles permissions, token fetch, and backend registration
  • Full notification lifecycle — foreground display (via notifee), background handling, cold-start tap detection
  • Offline event queue — notification events queued when offline, flushed automatically on next launch
  • Token auto-refresh — listens for FCM token rotation and re-registers silently
  • Typed event emitter — strongly-typed events for permission changes, token refresh, and notification interactions
  • Permission polling — detects permission revocation in the background without blocking the main thread

Requirements

Peer dependencyVersion
react-native>=0.71.0
@react-native-async-storage/async-storage>=1.19.0
@react-native-firebase/app>=20.0.0
@react-native-firebase/messaging>=20.0.0
@notifee/react-native>=7.0.0 (optional but strongly recommended)
@expo/config-plugins>=7.0.0 (Expo only)

@react-native-firebase/* and @notifee/react-native are optional at runtime — the SDK degrades gracefully if they are absent, but push notifications will not function without them.

Installation

npm install @circlehq/push-react-native
# or
yarn add @circlehq/push-react-native

Install required peer dependencies:

npm install @react-native-async-storage/async-storage \
            @react-native-firebase/app \
            @react-native-firebase/messaging \
            @notifee/react-native

Setup

Before using the SDK, upload your Firebase Service Account JSON on the Circle dashboard (Settings → Push Notifications). Circle stores it and uses it to hand back your platform-specific config on demand — you never put Firebase credentials in your app's source code.

Expo

Add the config plugin to your app.json or app.config.ts with your Circle API key:

{
  "plugins": [
    ["@circlehq/push-react-native/plugin", {
      "circleApiKey": "YOUR_CIRCLE_API_KEY",
      "androidNotificationIcon": "./assets/notification_icon.png"
    }]
  ]
}

At expo prebuild time, the plugin fetches your config from Circle's API and:

  • Android: writes google-services.json, applies the Google Services Gradle plugin, adds POST_NOTIFICATIONS permission
  • iOS: writes GoogleService-Info.plist, adds it to Xcode resources, calls FirebaseApp.configure() in AppDelegate, sets UIBackgroundModes and aps-environment

Then run prebuild:

npx expo prebuild

React Native CLI

Run the setup script with your Circle API key from your project root — it fetches google-services.json and GoogleService-Info.plist from Circle's API and writes them into android/app/ and ios/<ProjectName>/:

npx circle-push-setup --api-key YOUR_CIRCLE_API_KEY

The script prints the remaining manual steps (Gradle plugin, AppDelegate, Xcode resource). Then follow the React Native Firebase setup guide and the notifee installation guide for the rest of the native configuration.

Background Handler (Required)

This import must be the very first line of your app entry file, before any React component or other import. It registers native background handlers synchronously at module evaluation time. If it runs too late, background push events will be silently dropped.

// index.js — FIRST LINE
import '@circlehq/push-react-native/background';

import { AppRegistry } from 'react-native';
import App from './App';

AppRegistry.registerComponent('MyApp', () => App);

For Expo Router, add it as the first line of app/_layout.tsx:

// app/_layout.tsx — FIRST LINE
import '@circlehq/push-react-native/background';

import { Stack } from 'expo-router';
// ...

Quick Start

import CirclePush from '@circlehq/push-react-native';

// 1. Initialize (call once, e.g. in App.tsx useEffect)
await CirclePush.init({
  apiKey: 'cir_live_xxxxxxxxxxxx',
});

// 2. Identify a user — triggers permission prompt and device registration
const device = await CirclePush.identify({
  email: 'user@example.com',
});

console.log(device.deviceId, device.pushToken);

API Reference

CirclePush.init(config)

Initialize the SDK. Must be called and resolved before any other method.

await CirclePush.init({
  apiKey: 'cir_live_xxxxxxxxxxxx',   // required
  debug: __DEV__,                     // verbose logging, default: false
});

Safe to call multiple times with the same config — subsequent calls are no-ops. Throws CirclePushConfigError if called with a different config after initialization.

Config options:

OptionTypeDefaultDescription
apiKeystringCircle External API key. Required.
apiBaseUrlstringhttps://api.circlehq.coOverride the Circle API base URL
debugbooleanfalseEnable verbose console logging
autoRequestPermissionbooleantrueAuto-prompt for permission on identify() when status is default
androidNotificationIconstring'notification_icon'Android small icon resource name from res/drawable/
androidChannelIdstring'circle-push-default'Android notification channel ID

CirclePush.identify(identity)

Register the current user's device. Handles the full flow: permission request → FCM token fetch → backend registration.

const device = await CirclePush.identify({
  email: 'user@example.com',    // at least one of email or phone is required
  phone: '+12125551234',
  firstName: 'Jane',
  lastName: 'Doe',
});
// device: { deviceId, contactId, pushToken, platform, isValid }
  • If the user's identity changes, the previous device is unregistered before registering the new one.
  • Same-identity calls are throttled to once per 60 seconds; the cached registration is returned immediately within that window.
  • Throws CirclePushPermissionError if notifications are denied or unsupported.

CirclePush.requestPermission()

Explicitly prompt the user for notification permission. Useful when autoRequestPermission is false.

const status = await CirclePush.requestPermission();
// 'granted' | 'denied' | 'default' | 'unsupported'

CirclePush.getPermissionState()

Returns the last known permission state synchronously (no async call).

const status = CirclePush.getPermissionState();

CirclePush.getToken()

Returns the stored FCM push token, or null if not yet obtained.

const token = await CirclePush.getToken();

CirclePush.refresh()

Re-fetches the FCM token and re-registers with the backend. Useful after a long session to ensure the token is current.

await CirclePush.refresh();

Returns null if the SDK has no identity or permission is not granted.

CirclePush.unregister()

Unregisters the device from the backend, deletes the local FCM token, and clears stored state.

await CirclePush.unregister();

CirclePush.on(event, callback)

Subscribe to SDK events. Returns an unsubscribe function.

const unsub = CirclePush.on('notificationClicked', ({ messageId, link, action }) => {
  if (link) router.push(link);
});

// Later:
unsub();

Available events:

EventPayloadDescription
notificationReceivedNotificationPayloadA push notification arrived while the app is in the foreground
notificationClickedNotificationClickPayloadUser tapped a notification or action button
notificationDismissedNotificationDismissedPayloadUser dismissed a notification
permissionChangePermissionStateNotification permission status changed
tokenRefresh{ oldToken, newToken }FCM token was rotated
errorCirclePushErrorA non-fatal internal error occurred

CirclePush.setDebug(enabled)

Toggle verbose logging at runtime.

CirclePush.setDebug(true);

Event Payload Types

interface NotificationPayload {
  title?: string;
  body?: string;
  image?: string;
  data?: Record<string, string>;
  actions?: NotificationAction[];
}

interface NotificationClickPayload {
  messageId?: string;
  campaignId?: string;
  link?: string;
  action?: string; // action button id, if an action button was tapped
}

interface NotificationDismissedPayload {
  messageId?: string;
  campaignId?: string;
}

Error Handling

All errors thrown from public methods are instances of CirclePushError with a stable code for programmatic branching:

import {
  CirclePushError,
  CirclePushPermissionError,
  CirclePushApiError,
} from '@circlehq/push-react-native';

try {
  await CirclePush.identify({ email: 'user@example.com' });
} catch (e) {
  if (e instanceof CirclePushPermissionError) {
    // e.code === 'permission/denied' | 'permission/unsupported'
    showPermissionPrompt();
  } else if (e instanceof CirclePushApiError) {
    // e.status is the HTTP status code
    console.error('API error', e.status, e.message);
  } else if (e instanceof CirclePushError) {
    console.error(e.code, e.message, e.retryable);
  }
}

Error classes:

ClassCodes
CirclePushConfigErrorconfig/missing_api_key, config/already_initialized
CirclePushValidationErrorvalidation/missing_identity, validation/invalid_email, validation/invalid_phone, validation/invalid_token
CirclePushPermissionErrorpermission/denied, permission/unsupported
CirclePushTokenErrortoken/fetch_failed, token/delete_failed
CirclePushApiErrorapi/4xx, api/5xx, api/network, api/rate_limited
CirclePushErrorruntime/not_initialized, runtime/unknown

Complete Example

// index.js
import '@circlehq/push-react-native/background'; // MUST BE FIRST
import { AppRegistry } from 'react-native';
import App from './App';
AppRegistry.registerComponent('MyApp', () => App);
// App.tsx
import { useEffect } from 'react';
import CirclePush, { CirclePushPermissionError } from '@circlehq/push-react-native';

export default function App() {
  useEffect(() => {
    async function setup() {
      await CirclePush.init({
        apiKey: 'cir_live_xxxxxxxxxxxx',
        debug: __DEV__,
      });

      try {
        await CirclePush.identify({ email: 'user@example.com' });
      } catch (e) {
        if (e instanceof CirclePushPermissionError) {
          console.warn('Notifications not permitted');
        }
      }
    }

    setup();

    const unsub = CirclePush.on('notificationClicked', ({ link, messageId }) => {
      console.log('Notification tapped:', messageId, link);
    });

    return () => unsub();
  }, []);

  return <YourApp />;
}

TypeScript

The SDK ships full TypeScript declarations. All public types are exported from the main entry:

import type {
  CirclePushConfig,
  Identity,
  RegisteredDevice,
  PermissionState,
  NotificationPayload,
  NotificationClickPayload,
  NotificationDismissedPayload,
  EventMap,
  EventName,
  Platform,
} from '@circlehq/push-react-native';

License

MIT

Keywords

circle

FAQs

Package last updated on 24 Jun 2026

Related posts