Expo SDK

Push notifications for Expo applications with managed workflow support

PushBase Expo SDK

The PushBase Expo SDK will provide seamless push notification integration for Expo applications, supporting both managed and bare workflows with optimized configuration.

Overview

The PushBase Expo SDK provides a complete wrapper on top of expo-notifications to simplify permissions, token management, message handling, and local notifications for Expo apps. It supports:

  • Expo push tokens (default) or native device tokens (APNs/FCM) via useNativeTokens
  • Automatic initialization (configurable) and listeners setup
  • Android notification channels
  • Local notifications, badge count, and initial notification retrieval
  • Typed configuration and event handlers

Expected Installation

npx expo install @pushbase/expo

Quick Start

import { PushBaseExpo } from "@pushbase/expo";
import * as Notifications from "expo-notifications";

const pushBase = PushBaseExpo.getInstance({
  projectId: "your-expo-project-id", // required for Expo push tokens
  debug: __DEV__,
  // autoInitialize: true (default)
});

// Optional: explicit init if you set autoInitialize: false
await pushBase.initialize();

// Request permissions and get token
const permissions = await pushBase.requestPermissions();
if (permissions.granted) {
  const token = await pushBase.getToken();
  console.log("Push token:", token); // { type: 'expo' | 'native', data, platform? }
}

Listeners

// Foreground notifications
const unSubMessage = pushBase.onMessage((message) => {
  console.log("Foreground message:", message);
});

// Background notifications
const unSubBg = pushBase.onBackgroundMessage((message) => {
  console.log("Background message:", message);
});

// Notification taps/responses
const unSubResp = pushBase.onNotificationResponse((response) => {
  console.log("Notification response:", response);
});

// Token refresh
const unSubToken = pushBase.onTokenRefresh((token) => {
  console.log("Token refreshed:", token);
});

// Error handling
const unSubErr = pushBase.onError((error) => {
  console.error("PushBaseExpo error:", error);
});

Local Notifications and Badge

// Schedule local notification
const id = await pushBase.scheduleNotification({
  content: { title: "Hello", body: "Welcome!" },
  trigger: { type: "timeInterval", seconds: 2 },
});

// Cancel notifications
await pushBase.cancelNotification(id);
await pushBase.cancelAllNotifications();

// Badge count
await pushBase.setBadgeCount(3);
const current = await pushBase.getBadgeCount();

Android Channel (optional)

await pushBase.createNotificationChannel({
  id: "promos",
  name: "Promotions",
  importance: "HIGH",
});

Native Tokens (optional)

const pb = PushBaseExpo.getInstance({
  projectId: "your-expo-project-id",
  useNativeTokens: true, // returns { type: 'native', data, platform }
});
const token = await pb.getToken();

State and Utilities

const state = pushBase.getState();
const hasPerm = await pushBase.hasPermissions();
const initial = await pushBase.getInitialNotification();

// Cleanup
pushBase.destroy();

Types and Utilities

import {
  PushBaseExpoConfig,
  ExpoPushMessage,
  ExpoPermissionStatus,
  ExpoSDKState,
  ExpoScheduleOptions,
  PushToken,
  ExpoUtils, // sendPushNotifications, getPushReceipts, sendSingleNotification
} from "@pushbase/expo";
  • ExpoUtils provides helper functions for server-side operations:
    • sendPushNotifications(): Send notifications to multiple recipients
    • getPushReceipts(): Retrieve delivery receipts for sent notifications
    • sendSingleNotification(): Send a single notification to a specific device

Expected Configuration

app.json / app.config.js

{
  "expo": {
    "name": "Your App",
    "plugins": [
      [
        "@pushbase/expo",
        {
          "mode": "development" // or "production"
        }
      ]
    ],
    "notification": {
      "icon": "./assets/notification-icon.png",
      "color": "#ffffff",
      "androidMode": "default",
      "androidCollapsedTitle": "#{unread_notifications} new interactions"
    }
  }
}

Expected Usage

Basic Setup

import { PushBase } from "@pushbase/expo";
import * as Notifications from "expo-notifications";

// Configure notification behavior
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: false,
    shouldSetBadge: false,
  }),
});

// Initialize PushBase
const pushBase = PushBase.getInstance();

// Initialize with configuration
await pushBase.initialize({
  // For managed workflow (uses Expo Push Notifications)
  projectId: "your-expo-project-id",

  // For bare workflow (uses FCM)
  firebaseConfig: {
    // Firebase config (optional, for bare workflow)
  },

  serverUrl: "https://your-server.com/api", // Optional
  debug: __DEV__, // Enable debug in development
});

Request Permissions

// Request notification permissions
const permissions = await pushBase.requestPermissions();

if (permissions.granted) {
  console.log("Notifications enabled!");

  // Get the push token
  const token = await pushBase.getToken();
  console.log("Push token:", token);
} else {
  console.log("Notifications denied");
}

Handle Notifications

// Listen for foreground notifications
pushBase.onMessage((message) => {
  console.log("Foreground notification:", message);

  // Show local notification if needed
  Notifications.scheduleNotificationAsync({
    content: {
      title: message.notification?.title || "New Message",
      body: message.notification?.body || "You have a new message",
      data: message.data,
    },
    trigger: null, // Show immediately
  });
});

// Listen for background notifications
pushBase.onBackgroundMessage((message) => {
  console.log("Background notification:", message);
});

// Listen for notification taps
pushBase.onNotificationTap((notification) => {
  console.log("Notification tapped:", notification);
  // Navigate to specific screen based on notification data
});

Topic Subscriptions

// Subscribe to topics
await pushBase.subscribeToTopic("announcements");
await pushBase.subscribeToTopic("user-updates");

// Unsubscribe from topics
await pushBase.unsubscribeFromTopic("announcements");

Error Handling

// Listen for errors
pushBase.onError((error) => {
  console.error("PushBase error:", error);
});

// Listen for token refresh
pushBase.onTokenRefresh((newToken) => {
  console.log("Token refreshed:", newToken);
});

Expo-Specific Features

Managed vs Bare Workflow

The SDK will automatically detect your workflow and use the appropriate push service:

// Managed Workflow
// Uses Expo Push Notifications automatically
const pushBase = PushBase.getInstance();
await pushBase.initialize({
  projectId: "your-expo-project-id",
});

// Bare Workflow
// Uses Firebase Cloud Messaging
const pushBase = PushBase.getInstance();
await pushBase.initialize({
  firebaseConfig: {
    // Your Firebase config
  },
});

Development vs Production

// Automatic environment detection
const pushBase = PushBase.getInstance();
await pushBase.initialize({
  projectId: "your-expo-project-id",
  environment: __DEV__ ? "development" : "production", // Auto-detected
});

EAS Build Integration

For EAS Build, the SDK will work seamlessly:

// eas.json
{
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal",
      "env": {
        "PUSHBASE_ENV": "development"
      }
    },
    "production": {
      "env": {
        "PUSHBASE_ENV": "production"
      }
    }
  }
}

Expected Types

interface ExpoPushBaseConfig {
  // For managed workflow
  projectId?: string;

  // For bare workflow
  firebaseConfig?: {
    apiKey: string;
    authDomain: string;
    projectId: string;
    storageBucket: string;
    messagingSenderId: string;
    appId: string;
  };

  serverUrl?: string;
  environment?: "development" | "production";
  debug?: boolean;
}

interface ExpoPermissionStatus {
  granted: boolean;
  status: "granted" | "denied" | "undetermined";
  canAskAgain: boolean;
  settings: {
    allowsAlert: boolean;
    allowsBadge: boolean;
    allowsSound: boolean;
    allowsDisplayInNotificationCenter: boolean;
    allowsDisplayInCarPlay: boolean;
    allowsDisplayOnLockScreen: boolean;
  };
}

interface ExpoPushMessage {
  messageId: string;
  data?: Record<string, any>;
  notification?: {
    title?: string;
    body?: string;
    sound?: boolean | string;
    badge?: number;
    categoryId?: string;
    subtitle?: string;
  };
  from?: string;
  sentTime?: number;
  ttl?: number;
}

Development Status

  • 🚧 In Development: Core Expo integration
  • 📋 Planned: Managed workflow implementation
  • 📋 Planned: Bare workflow support
  • 📋 Planned: EAS Build optimization
  • 📋 Planned: TypeScript definitions
  • 📋 Planned: Testing and documentation

Expo Compatibility

The SDK will support:

  • Expo SDK 50+: Full feature support
  • Managed Workflow: Expo Push Notifications
  • Bare Workflow: Firebase Cloud Messaging
  • EAS Build: Optimized build configuration
  • Expo Development Client: Development testing

Get Notified

Want to be notified when the Expo SDK is ready?

Migration from Expo Notifications

When the Expo SDK is released, migrating from direct Expo Notifications will be simple:

Before (Direct Expo Notifications)

import * as Notifications from "expo-notifications";
import { registerForPushNotificationsAsync } from "./registerForPushNotifications";

// Manual setup
const token = await registerForPushNotificationsAsync();

// Manual listeners
Notifications.addNotificationReceivedListener((notification) => {
  console.log("Notification received:", notification);
});

Notifications.addNotificationResponseReceivedListener((response) => {
  console.log("Notification tapped:", response);
});

After (PushBase Expo SDK)

import { PushBase } from "@pushbase/expo";

const pushBase = PushBase.getInstance();
await pushBase.initialize({ projectId: "your-project-id" });

// Automatic setup
const permissions = await pushBase.requestPermissions();
const token = await pushBase.getToken();

// Simplified listeners
pushBase.onMessage((message) => {
  console.log("Message received:", message);
});

pushBase.onNotificationTap((notification) => {
  console.log("Notification tapped:", notification);
});