xtremepush-expo-plugin
v1.1.1
Published
XtremePush Expo Config Plugin
Downloads
96
Maintainers
Readme
XtremePush Expo Plugin
A config plugin for Expo applications that integrates XtremePush SDK functionality with full React Native module support for both iOS and Android platforms.
Table of Contents
Requirements
System Requirements
- Node.js: 18.0 or higher
- Expo SDK: 53.0 or higher
- React Native: 0.73.0 or higher
iOS Requirements
- iOS Deployment Target: 13.0 or higher
- Xcode: 14.0 or higher
- CocoaPods: Latest version
- Apple Developer Account with Push Notification capability enabled
Android Requirements
- Minimum SDK: 21 (Android 5.0)
- Target SDK: 34 (Android 14)
- Gradle: 8.0 or higher
- Google Play Services: Required for FCM
Platform Support
| Platform | Minimum Version | Push Notifications | In-App Messages | Location Services | |----------|----------------|-------------------|-----------------|-------------------| | iOS | 13.0+ | ✅ | ✅ | ✅ | | Android | 5.0+ (API 21) | ✅ | ✅ | ✅ |
Installation
Step 1: Install the Plugin
# Using npm
npm install xtremepush-expo-plugin
# Using yarn
yarn add xtremepush-expo-pluginStep 2: Configure Your App
Add the plugin to your app.json or app.config.js:
{
"expo": {
"name": "YourAppName",
"plugins": [
[
"xtremepush-expo-plugin",
{
"applicationKey": "YOUR_XTREMEPUSH_APP_KEY",
"iosAppKey": "YOUR_IOS_APP_KEY",
"androidAppKey": "YOUR_ANDROID_APP_KEY",
"googleSenderId": "YOUR_FCM_SENDER_ID",
"enableDebugLogs": true,
"enableLocationServices": true,
"enablePushPermissions": true
}
]
]
}
}Step 3: Prebuild Your Project
After adding the plugin configuration, rebuild your native projects:
# Clean rebuild (recommended for first-time setup)
npx expo prebuild --clean
# For iOS, install CocoaPods dependencies
cd ios && pod install && cd ..Step 4: Run Your Application
# For iOS
npx expo run:ios
# For Android
npx expo run:androidPlatform Setup
iOS Setup
1. Basic Configuration
The plugin automatically configures these capabilities in your iOS project:
- Push Notifications
- App Groups (for rich media notifications)
2. App Groups Setup (Required for Rich Media)
For rich media push notifications with images and attachments, you need to configure App Groups:
A. In Apple Developer Console:
Log into your Apple Developer Account
- Go to developer.apple.com
- Navigate to "Certificates, Identifiers & Profiles"
Create App Group:
- Click "Identifiers" → "+" → "App Groups"
- Identifier:
group.{your-bundle-id}.xtremepush - Description: "XtremePush Rich Media Notifications"
- Example:
group.com.yourcompany.yourapp.xtremepush
Enable App Groups for your App ID:
- Click "Identifiers" → Select your App ID
- Check "App Groups" capability
- Click "Configure" → Select your created App Group
- Save changes
Update Provisioning Profiles:
- Regenerate and download updated provisioning profiles
- Install them in Xcode or add to your CI/CD
⚠️ Important: If your iOS build fails after enabling App Groups, you need to regenerate your provisioning profiles. This commonly occurs when App Groups are added to an existing App ID.
Solution:
# Clear your iOS build and regenerate with updated provisioning profiles npx expo prebuild --clean --platform ios # Install updated CocoaPods dependencies cd ios && pod install && cd .. # Rebuild your project npx expo run:iosThis ensures your local build uses the updated provisioning profiles that include the App Groups capability.
B. Configuration in Your Plugin:
{
"expo": {
"plugins": [
[
"xtremepush-expo-plugin",
{
"applicationKey": "YOUR_APP_KEY",
"googleSenderId": "YOUR_SENDER_ID",
"enableRichMedia": true,
"iosAppGroup": "group.com.yourcompany.yourapp.xtremepush"
}
]
]
}
}C. EAS Build Configuration (if using EAS):
Add to your app.json for EAS builds:
{
"expo": {
"extra": {
"eas": {
"projectId": "your-project-id",
"build": {
"experimental": {
"ios": {
"appExtensions": [
{
"targetName": "XtremePushNotificationServiceExtension",
"bundleIdentifier": "com.yourcompany.yourapp.XtremePushNotificationServiceExtension",
"entitlements": {
"com.apple.security.application-groups": [
"group.com.yourcompany.yourapp.xtremepush"
]
}
}
]
}
}
}
}
}
}
}4. Verification Steps
After setup, verify your configuration:
# Check if Service Extension was created
ls ios/XtremePushNotificationServiceExtension/
# Verify Podfile includes XtremePush SDK
grep -r "Xtremepush-iOS-SDK" ios/Podfile
# Check App Groups in entitlements
grep -r "com.apple.security.application-groups" ios/Android Setup
1. Firebase Configuration
Create Firebase Project:
- Go to Firebase Console
- Create new project or use existing one
Add Android App:
- Package name must match your
android.packagein app.json - Download
google-services.json - Place in your project root (not in android/ folder)
- Package name must match your
Get FCM Server Key:
- Project Settings → Cloud Messaging → Server Key
- Use this as
googleSenderIdin plugin configuration
2. Permissions
The plugin automatically adds required permissions to AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.VIBRATE" />
<!-- Added if location services enabled -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />Configuration
Advanced Configuration
// app.config.js
export default {
expo: {
plugins: [
[
"xtremepush-expo-plugin",
{
// Required
"applicationKey": "YOUR_APP_KEY",
"googleSenderId": "YOUR_FCM_SENDER_ID",
// Platform-specific keys (optional)
"iosAppKey": "IOS_SPECIFIC_KEY",
"androidAppKey": "ANDROID_SPECIFIC_KEY",
// Server configuration (optional)
"useUsServer": true,
// Features (optional)
"enableDebugLogs": true,
"enableLocationServices": false,
"enablePushPermissions": true,
"enableCrashReporting": false,
// iOS specific (optional)
"iosPermissions": {
"NSLocationWhenInUseUsageDescription": "We need your location to provide relevant offers",
"NSLocationAlwaysAndWhenInUseUsageDescription": "We need your location to send location-based notifications"
},
// Android specific (optional)
"androidIcon": "ic_notification",
"androidIconColor": "#FF0000"
}
]
]
}
};Rich Media Configuration
Enable rich media notifications with images and attachments:
// app.config.js
export default {
expo: {
ios: {
bundleIdentifier: "com.yourcompany.yourapp"
},
plugins: [
[
"xtremepush-expo-plugin",
{
"applicationKey": "YOUR_APP_KEY",
"googleSenderId": "YOUR_SENDER_ID",
// Enable rich media
"enableRichMedia": true,
// iOS App Group (required for rich media)
"iosAppGroup": "group.com.yourcompany.yourapp.xtremepush",
// Service Extension Configuration
"extensionTargetName": "XtremePushNotificationServiceExtension",
"extensionBundleSuffix": "XtremePushNotificationServiceExtension",
"iosDeploymentTarget": "15.0"
}
]
]
}
};Configuration Options
| Option | Type | Required | Default | Description |
|--------|------|----------|---------|-------------|
| applicationKey | string | Yes | - | Your XtremePush application key |
| googleSenderId | string | Yes* | - | FCM Sender ID (*Required for Android) |
| iosAppKey | string | No | applicationKey | iOS-specific application key |
| androidAppKey | string | No | applicationKey | Android-specific application key |
| serverUrl | string | No | Default EU server | Custom XtremePush server URL |
| useUsServer | boolean | No | false | Use US data center (sets serverUrl to https://sdk.us.xtremepush.com) |
| usServerUrl | string | No | https://sdk.us.xtremepush.com | Custom US server URL (used when useUsServer is true) |
| enablePinning | boolean | No | false | Enable SSL certificate pinning (iOS only, required for US region) |
| certificatePath | string | No | - | Path to SSL certificate file (.der) for certificate pinning |
| enableDebugLogs | boolean | No | false | Enable SDK debug logging |
| enableLocationServices | boolean | No | false | Enable location tracking |
| enablePushPermissions | boolean | No | true | Auto-request push permissions |
| enableRichMedia | boolean | No | false | Enable rich media notifications (iOS) |
| iosAppGroup | string | No | Auto-generated | iOS App Group identifier |
| extensionTargetName | string | No | XtremePushNotificationServiceExtension | Service Extension target name |
| iosDeploymentTarget | string | No | 15.0 | iOS deployment target |
| devTeam | string | No | - | Apple Developer Team ID |
Server Configuration (US Region Support)
By default, the XtremePush SDK connects to the EU data center. If your account is hosted on the US data center, you need to configure the server URL.
Option 1: Use US Server Flag (Recommended)
// app.config.js
export default {
expo: {
plugins: [
[
"xtremepush-expo-plugin",
{
"applicationKey": "YOUR_APP_KEY",
"googleSenderId": "YOUR_SENDER_ID",
"useUsServer": true // ← Automatically uses US data center
}
]
]
}
};Option 2: Custom Server URL
// app.config.js
export default {
expo: {
plugins: [
[
"xtremepush-expo-plugin",
{
"applicationKey": "YOUR_APP_KEY",
"googleSenderId": "YOUR_SENDER_ID",
"serverUrl": "https://sdk.us.xtremepush.com" // ← Custom URL
}
]
]
}
};Important Notes:
- The server URL is configured at build time via native code injection
- After changing server configuration, run
npx expo prebuild --clean - Both iOS and Android will use the same server URL
- If both
serverUrlanduseUsServerare specified,serverUrltakes precedence
SSL Certificate Pinning (US Region Required)
If your project is provisioned in the US region, you must implement Public Key Pinning for security. This validates that the SSL certificate from the server matches an expected certificate bundled with your app.
Step 1: Obtain the Certificate
Contact XtremePush support to obtain the cert.der file for the US region server.
Step 2: Add Certificate to Your Project
Place the certificate file (e.g., cert.der) in your project root or assets folder:
your-project/
├── assets/
│ └── cert.der ← SSL certificate here
├── app.json
└── package.jsonStep 3: Configure Certificate Pinning
// app.config.js
export default {
expo: {
plugins: [
[
"xtremepush-expo-plugin",
{
"applicationKey": "YOUR_APP_KEY",
"googleSenderId": "YOUR_SENDER_ID",
"useUsServer": true, // ← Enable US server
"enablePinning": true, // ← Enable certificate pinning
"certificatePath": "assets/cert.der" // ← Path to certificate
}
]
]
}
};Step 4: Rebuild
npx expo prebuild --clean
cd ios && pod install && cd ..
npx expo run:iosCertificate Pinning Notes:
- Currently only supported on iOS (Android support coming soon)
- Certificate file is automatically copied to the iOS bundle during build
- The path can be absolute or relative to project root
- Certificate file must have
.derextension - If certificate is not found, a warning will be logged but the build will continue
Usage
Import the Module
// Import default export
import Xtremepush from './xtremepush';
// Or import specific functions
import {
hitEvent,
hitTag,
setUser,
openInbox,
requestNotificationPermissions
} from './xtremepush';Basic Integration
import { useEffect } from 'react';
import Xtremepush from './xtremepush';
export default function App() {
useEffect(() => {
// Set user identifier
Xtremepush.setUser('[email protected]');
// Track app open event
Xtremepush.hitEvent('app_opened');
// Request push permissions (iOS only)
Xtremepush.requestNotificationPermissions();
}, []);
return <YourApp />;
}User Management
// Set user ID (email, username, or unique ID)
Xtremepush.setUser("[email protected]");
// Set external ID (e.g., your CRM ID)
Xtremepush.setExternalId("CRM-12345");Event Tracking
// Track simple events
Xtremepush.hitEvent("purchase_completed");
Xtremepush.hitEvent("article_read");
Xtremepush.hitEvent("level_completed");
// Track tags (user properties)
Xtremepush.hitTag("premium_user");
Xtremepush.hitTag("newsletter_subscriber");
// Track tags with values
Xtremepush.hitTagWithValue("user_level", "gold");
Xtremepush.hitTagWithValue("purchase_amount", "99.99");
Xtremepush.hitTagWithValue("cart_items", "3");Push Notifications
// Request notification permissions
Xtremepush.requestNotificationPermissions();
// Open message inbox
Xtremepush.openInbox();Initial Notification Handling
The getInitialNotification() method allows you to capture push notification payloads when your app is opened from a terminated state. This provides a reliable alternative to React Native's Linking.getInitialURL() for notification-driven deep linking.
Basic Usage
import { useEffect, useState } from 'react';
import Xtremepush from './xtremepush';
export default function App() {
const [notificationData, setNotificationData] = useState(null);
useEffect(() => {
const checkInitialNotification = async () => {
try {
const payload = await Xtremepush.getInitialNotification();
if (payload) {
console.log('App opened from notification:', payload);
setNotificationData(payload);
// Handle the notification data
handleNotificationPayload(payload);
}
} catch (error) {
console.error('Error getting initial notification:', error);
}
};
checkInitialNotification();
}, []);
const handleNotificationPayload = (payload) => {
// Handle deeplink navigation
if (payload.deeplink) {
// Navigate to specific screen
console.log('Navigate to:', payload.deeplink);
}
// Track notification interaction
if (payload.campaignId) {
Xtremepush.hitEvent('notification_opened', {
campaignId: payload.campaignId,
notificationId: payload.id
});
}
};
return <YourApp />;
}React Navigation Integration
import { useEffect } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import Xtremepush from './xtremepush';
const Stack = createStackNavigator();
export default function App() {
const [initialRoute, setInitialRoute] = useState('Home');
const [initialParams, setInitialParams] = useState({});
useEffect(() => {
const handleInitialNotification = async () => {
try {
const payload = await Xtremepush.getInitialNotification();
if (payload && payload.deeplink) {
// Parse deeplink and set initial route
const route = parseDeeplink(payload.deeplink);
if (route) {
setInitialRoute(route.screen);
setInitialParams({
...route.params,
notificationData: payload
});
}
}
} catch (error) {
console.error('Error handling initial notification:', error);
}
};
handleInitialNotification();
}, []);
const parseDeeplink = (deeplink) => {
// Example: "myapp://product/123" or "myapp://article/456"
const url = new URL(deeplink);
const pathSegments = url.pathname.split('/').filter(Boolean);
if (pathSegments.length >= 2) {
const [section, id] = pathSegments;
switch (section) {
case 'product':
return { screen: 'ProductDetail', params: { productId: id } };
case 'article':
return { screen: 'ArticleDetail', params: { articleId: id } };
case 'profile':
return { screen: 'Profile', params: { userId: id } };
default:
return { screen: 'Home', params: {} };
}
}
return null;
};
return (
<NavigationContainer>
<Stack.Navigator initialRouteName={initialRoute}>
<Stack.Screen
name="Home"
component={HomeScreen}
/>
<Stack.Screen
name="ProductDetail"
component={ProductDetailScreen}
initialParams={initialParams}
/>
<Stack.Screen
name="ArticleDetail"
component={ArticleDetailScreen}
initialParams={initialParams}
/>
</Stack.Navigator>
</NavigationContainer>
);
}TypeScript Usage
import { useEffect, useState } from 'react';
import Xtremepush from './xtremepush';
// Type definition for notification payload
interface XtremePushNotificationPayload {
id?: string;
campaignId?: string;
title?: string;
text?: string;
deeplink?: string;
data?: { [key: string]: any };
platform?: 'android' | 'ios';
receivedAt?: number;
badge?: number;
[key: string]: any;
}
export default function App() {
const [notification, setNotification] = useState<XtremePushNotificationPayload | null>(null);
useEffect(() => {
const getInitialNotification = async (): Promise<void> => {
try {
const payload: XtremePushNotificationPayload | null =
await Xtremepush.getInitialNotification();
if (payload) {
setNotification(payload);
// Type-safe access to payload properties
const { deeplink, campaignId, data } = payload;
if (deeplink) {
handleDeeplink(deeplink);
}
if (campaignId) {
trackNotificationOpened(campaignId);
}
if (data?.customField) {
handleCustomData(data.customField);
}
}
} catch (error) {
console.error('Failed to get initial notification:', error);
}
};
getInitialNotification();
}, []);
return <YourApp />;
}Payload Structure
The getInitialNotification() method returns a payload object with the following structure:
{
// Core notification fields
"id": "1234567", // Notification ID
"campaignId": "1234567", // Campaign ID
"title": "Special Offer!", // Notification title
"text": "Check out our latest deals", // Notification message
"deeplink": "myapp://products/sale", // Deep link URL
// Platform information
"platform": "ios", // or "android"
"receivedAt": 1640995200000, // Timestamp when received
// iOS specific
"badge": 1, // Badge count (iOS only)
// Custom data
"data": {
"productId": "12345",
"category": "electronics",
"discount": "20%",
"customField": "customValue"
}
}Best Practices
Call Early in App Lifecycle
// Call in App.js or your root component useEffect(() => { checkInitialNotification(); }, []);Handle Null/Empty Responses
const payload = await Xtremepush.getInitialNotification(); if (payload) { // Handle notification } else { // App opened normally (not from notification) }Combine with Navigation
// Wait for navigation to be ready before handling deeplinks if (payload && navigationRef.isReady()) { handleDeeplink(payload.deeplink); }Track Notification Interactions
if (payload) { Xtremepush.hitEvent('notification_opened'); Xtremepush.hitTagWithValue('campaign_id', payload.campaignId); }Error Handling
try { const payload = await Xtremepush.getInitialNotification(); // Handle payload } catch (error) { console.error('Notification handling failed:', error); }
iOS Common Issues
- Wrong Environment: Ensure using Development cert for testing, Production for App Store
- Expired Certificates: Certificates are valid for 1 year, renew before expiration
- Bundle ID Mismatch: Certificate must match app's Bundle ID exactly
Android Common Issues
- Invalid Server Key: Ensure using Server Key, not Web API Key
- Package Name Mismatch: Firebase package name must match your app exactly
- Missing google-services.json: File must be in project root
License
This project is licensed under the MIT License.
MIT License
Copyright (c) 2025 XtremePush