XtremePush Expo Config Plugin
npm install xtremepush-expo-pluginA config plugin for Expo applications that integrates XtremePush SDK functionality with full React Native module support for both iOS and Android platforms.
- Requirements
- Platform Support
- Installation
- Platform Setup
- Configuration
- Usage
- Import the Module
- Basic Integration
- User Management
- Event Tracking
- Push Notifications
- Initial Notification Handling
- License
| Platform | Minimum Version | Push Notifications | In-App Messages | Location Services |
|----------|----------------|-------------------|-----------------|-------------------|
| iOS | 13.0+ | ✅ | ✅ | ✅ |
| Android | 5.0+ (API 21) | ✅ | ✅ | ✅ |
``bashUsing npm
npm install xtremepush-expo-plugin
$3
Add the plugin to your
app.json or app.config.js:`json
{
"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
}
]
]
}
}
`$3
After adding the plugin configuration, rebuild your native projects:
`bash
Clean rebuild (recommended for first-time setup)
npx expo prebuild --cleanFor iOS, install CocoaPods dependencies
cd ios && pod install && cd ..
`$3
`bash
For iOS
npx expo run:iosFor Android
npx expo run:android
`Platform Setup
$3
#### 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:
1. Log into your Apple Developer Account
- Go to developer.apple.com
- Navigate to "Certificates, Identifiers & Profiles"
2. Create App Group:
- Click "Identifiers" → "+" → "App Groups"
- Identifier:
group.{your-bundle-id}.xtremepush
- Description: "XtremePush Rich Media Notifications"
- Example: group.com.yourcompany.yourapp.xtremepush3. 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
4. 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:
>
`bash
> # 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:ios
> `
>
> This ensures your local build uses the updated provisioning profiles that include the App Groups capability.B. Configuration in Your Plugin:
`json
{
"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:`json
{
"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:
`bash
Check if Service Extension was created
ls ios/XtremePushNotificationServiceExtension/Verify Podfile includes XtremePush SDK
grep -r "Xtremepush-iOS-SDK" ios/PodfileCheck App Groups in entitlements
grep -r "com.apple.security.application-groups" ios/
`$3
#### 1. Firebase Configuration
1. Create Firebase Project:
- Go to Firebase Console
- Create new project or use existing one
2. Add Android App:
- Package name must match your
android.package in app.json
- Download google-services.json
- Place in your project root (not in android/ folder)3. Get FCM Server Key:
- Project Settings → Cloud Messaging → Server Key
- Use this as
googleSenderId in plugin configuration#### 2. Permissions
The plugin automatically adds required permissions to
AndroidManifest.xml:
`xml
`Configuration
$3
`javascript
// 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"
}
]
]
}
};
`$3
Enable rich media notifications with images and attachments:
`javascript
// 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"
}
]
]
}
};
`$3
| 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) |
| enableInAppMessaging | boolean | No | true | Enable in-app messaging |
| 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 |$3
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)
`javascript
// 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
`javascript
// 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 serverUrl and useUsServer are specified, serverUrl takes precedence$3
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.json
`#### Step 3: Configure Certificate Pinning
`javascript
// 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
`bash
npx expo prebuild --clean
cd ios && pod install && cd ..
npx expo run:ios
`Certificate 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
.der extension
- If certificate is not found, a warning will be logged but the build will continueUsage
$3
`javascript
// Import default export
import Xtremepush from './xtremepush';// Or import specific functions
import {
hitEvent,
hitTag,
setUser,
openInbox,
requestNotificationPermissions
} from './xtremepush';
`$3
`javascript
import { useEffect } from 'react';
import Xtremepush from './xtremepush';export default function App() {
useEffect(() => {
// Set user identifier
Xtremepush.setUser('user@example.com');
// Track app open event
Xtremepush.hitEvent('app_opened');
// Request push permissions (iOS only)
Xtremepush.requestNotificationPermissions();
}, []);
return ;
}
`$3
`javascript
// Set user ID (email, username, or unique ID)
Xtremepush.setUser("user@example.com");// Set external ID (e.g., your CRM ID)
Xtremepush.setExternalId("CRM-12345");
`$3
`javascript
// 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");
`$3
`javascript
// Request notification permissions
Xtremepush.requestNotificationPermissions();// Open message inbox
Xtremepush.openInbox();
`$3
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
`javascript
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 ;
}
`#### React Navigation Integration
`javascript
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 (
name="Home"
component={HomeScreen}
/>
name="ProductDetail"
component={ProductDetailScreen}
initialParams={initialParams}
/>
name="ArticleDetail"
component={ArticleDetailScreen}
initialParams={initialParams}
/>
);
}
`#### TypeScript Usage
`typescript
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(null);
useEffect(() => {
const getInitialNotification = async (): Promise => {
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 ;
}
`
#### Payload Structure
The
getInitialNotification() method returns a payload object with the following structure:`javascript
{
// 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
1. Call Early in App Lifecycle
`javascript
// Call in App.js or your root component
useEffect(() => {
checkInitialNotification();
}, []);
`2. Handle Null/Empty Responses
`javascript
const payload = await Xtremepush.getInitialNotification();
if (payload) {
// Handle notification
} else {
// App opened normally (not from notification)
}
`3. Combine with Navigation
`javascript
// Wait for navigation to be ready before handling deeplinks
if (payload && navigationRef.isReady()) {
handleDeeplink(payload.deeplink);
}
`4. Track Notification Interactions
`javascript
if (payload) {
Xtremepush.hitEvent('notification_opened');
Xtremepush.hitTagWithValue('campaign_id', payload.campaignId);
}
`5. Error Handling
`javascript
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.
``Copyright (c) 2025 XtremePush