Developers · API v1.0.0

React Native (Bare / CLI) Integration

This guide is for (non-Expo) React Native projects created with npx @react-native-community/cli init. For the general API reference, see the Overview.

Supported versions: React Native 0.74+ (including the New Architecture) · iOS 13.4+ · Android 7.0+ (API 24) · Node 18+

Because NotiPilot 1.0.0 delivers notifications through the Expo Push Service, each device needs an Expo Push Token. In a bare React Native project, there are two ways to get one:

Method When to use it
A. expo-notifications (recommended) For most projects. Expo modules are added to the bare project; no migration to Expo is required.
B. @react-native-firebase/messaging + token conversion If the project already uses Firebase Messaging.

With either method, you need to create an EAS project (npx eas-cli init) and upload your FCM V1 / APNs credentials with eas credentials. The EAS project ID is the value you enter in the Expo Project ID field of the NotiPilot dashboard and send to the API as app_id.


1. Installation

Terminal
npx install-expo-modules@latest
npx expo install expo-notifications expo-device expo-secure-store expo-localization expo-crypto expo-application
cd ios && pod install && cd ..

Android

  1. Download google-services.json from the Firebase console and place it in android/app/.
  2. android/build.gradle → dependencies { classpath 'com.google.gms:google-services:4.4.2' }
  3. android/app/build.gradle → add apply plugin: 'com.google.gms.google-services' at the bottom

iOS (Xcode → Signing & Capabilities)

  1. Add the Push Notifications capability.
  2. Under Background Modes, check Remote notifications.

2. Client code

You can use the src/notipilot.ts file from the Expo guide as is. Since a bare project has no expo-constants manifest, just hard-code the project ID and read app_version from expo-application:

TypeScript
import * as Application from 'expo-application';

// Same as the "Expo Project ID" in the NotiPilot dashboard
const PROJECT_ID = 'YOUR-EAS-PROJECT-ID';

// attributes inside registerDevice():
//   app_version: Application.nativeApplicationVersion,

Method B — Firebase Messaging + Expo token conversion

If @react-native-firebase/messaging is already installed in your project, you can convert the device's native token into an Expo token and send it to NotiPilot.

Terminal
npm i @react-native-firebase/app @react-native-firebase/messaging react-native-get-random-values uuid @react-native-async-storage/async-storage react-native-localize react-native-device-info
TypeScript
import 'react-native-get-random-values';
import { v4 as uuidv4 } from 'uuid';
import messaging from '@react-native-firebase/messaging';
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as RNLocalize from 'react-native-localize';
import DeviceInfo from 'react-native-device-info';
import { Platform, PermissionsAndroid } from 'react-native';

const NOTIPILOT_BASE_URL = 'https://app.notipilot.com/api/v1';
const PROJECT_ID = 'YOUR-EAS-PROJECT-ID';

async function getDeviceUid() {
  let uid = await AsyncStorage.getItem('notipilot_device_uid');
  if (!uid) {
    uid = uuidv4();
    await AsyncStorage.setItem('notipilot_device_uid', uid);
  }
  return uid;
}

async function requestPermission(): Promise<boolean> {
  if (Platform.OS === 'android' && Platform.Version >= 33) {
    const r = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS);
    if (r !== PermissionsAndroid.RESULTS.GRANTED) return false;
  }
  const status = await messaging().requestPermission();
  return (
    status === messaging.AuthorizationStatus.AUTHORIZED ||
    status === messaging.AuthorizationStatus.PROVISIONAL
  );
}

/** Converts the native FCM/APNs token into an Expo Push Token. */
async function toExpoPushToken(deviceUid: string): Promise<string> {
  const isIOS = Platform.OS === 'ios';
  const deviceToken = isIOS ? await messaging().getAPNSToken() : await messaging().getToken();
  if (!deviceToken) throw new Error('Could not get native push token');

  const res = await fetch('https://exp.host/--/api/v2/push/getExpoPushToken', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      type: isIOS ? 'apns' : 'fcm',
      deviceId: deviceUid.toLowerCase(),
      development: __DEV__ && isIOS, // iOS debug builds use the APNs sandbox
      appId: DeviceInfo.getBundleId(), // Android package / iOS bundle id
      deviceToken,
      projectId: PROJECT_ID,
    }),
  });
  if (!res.ok) throw new Error(`Expo token conversion failed: ${res.status}`);
  const json = await res.json();
  return json.data.expoPushToken; // "ExponentPushToken[...]"
}

export async function registerDevice(extra: Record<string, string | number | boolean | null> = {}) {
  if (!(await requestPermission())) return null;

  const deviceUid = await getDeviceUid();
  const token = await toExpoPushToken(deviceUid);
  const locale = RNLocalize.getLocales()[0];

  const res = await fetch(`${NOTIPILOT_BASE_URL}/register-device`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      app_id: PROJECT_ID,
      device_uid: deviceUid,
      token,
      platform: Platform.OS,
      provider: 'expo',
      attributes: {
        locale: locale?.languageCode ?? null,
        country: RNLocalize.getCountry(),
        app_version: DeviceInfo.getVersion(),
        ...extra,
      },
    }),
  });
  return res.json();
}

// Re-register when the token is refreshed
messaging().onTokenRefresh(() => registerDevice().catch(() => {}));

exp.host/--/api/v2/push/getExpoPushToken is the endpoint used internally by Expo's own expo-notifications library; Expo does not document it separately. Method A is the safest long-term option.

For the identify-device call and error handling, you can reuse the identify, logout, and post functions from the Expo guide as is.

Checklist

  • EAS project created, and its project ID entered as the Expo Project ID in the dashboard
  • FCM V1 + APNs credentials uploaded with eas credentials
  • POST_NOTIFICATIONS permission requested on Android 13+
  • Push Notifications + Remote notifications capabilities enabled on iOS
  • country and, if possible, city sent as attributes